{"_id":"@dmytromykhailiuk/typed-local-storage","_rev":"2-e97181703a7a6678cb2698ac6079f81d","name":"@dmytromykhailiuk/typed-local-storage","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@dmytromykhailiuk/typed-local-storage","version":"1.0.0","keywords":["localstorage","local-storage","storage","web-storage","typed","typescript","type-safe","persistence","cross-tab","storage-event","ssr","ssr-safe","zero-dependencies"],"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","_id":"@dmytromykhailiuk/typed-local-storage@1.0.0","maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"homepage":"https://github.com/dmytromykhailiuk/typed-local-storage#readme","bugs":{"url":"https://github.com/dmytromykhailiuk/typed-local-storage/issues"},"dist":{"shasum":"77f746d324adf8d273dba68f0279cd524aadbe54","tarball":"https://registry.npmjs.org/@dmytromykhailiuk/typed-local-storage/-/typed-local-storage-1.0.0.tgz","fileCount":9,"integrity":"sha512-gAgCM9eRb7WLOFuZ6yd5S5RGgCKtfCg0MJyxVZZRbjAoPodoyMkcjIHtvv12B0yCmQjsXxeJACyxxhTEAq0psQ==","signatures":[{"sig":"MEYCIQDyIuuZSvOVTd3haAME/8R2CqLQQD6PK2o86YsqzLkYxAIhAIt7MlpUTaVx2LG9YtlnXaflH8iZKd9KhsUXkYq2nr5/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":50176},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"b6d83cf38e60f6054f88ed3fa2e33e4dedcd236a","scripts":{"dev":"tsup --watch","lint":"biome check .","test":"vitest run","build":"tsup","format":"biome format --write .","lint:fix":"biome check --write .","typecheck":"tsc --noEmit","playground":"vite --config vite.playground.config.ts","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"},"repository":{"url":"git+https://github.com/dmytromykhailiuk/typed-local-storage.git","type":"git"},"_npmVersion":"11.6.2","description":"Typed localStorage with initial values, groups and cross-tab subscriptions. SSR-safe, dependency-free, a few hundred bytes.","directories":{},"sideEffects":false,"_nodeVersion":"24.12.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vite":"^5.4.11","jsdom":"^25.0.1","vitest":"^2.1.8","typescript":"^5.7.3","@types/node":"^22.10.5","@biomejs/biome":"^1.9.4"},"_npmOperationalInternal":{"tmp":"tmp/typed-local-storage_1.0.0_1784992817976_0.1499081101196318","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@dmytromykhailiuk/typed-local-storage","version":"1.0.1","description":"Typed localStorage with initial values, groups and cross-tab subscriptions. SSR-safe, dependency-free, a few hundred bytes.","type":"module","sideEffects":false,"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","keywords":["localstorage","local-storage","storage","web-storage","typed","typescript","type-safe","persistence","cross-tab","storage-event","ssr","ssr-safe","zero-dependencies"],"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"scripts":{"build":"tsup","dev":"tsup --watch","playground":"vite --config vite.playground.config.ts","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","lint":"biome check .","lint:fix":"biome check --write .","format":"biome format --write .","prepublishOnly":"npm run build"},"engines":{"node":">=18"},"devDependencies":{"@biomejs/biome":"^1.9.4","@types/node":"^22.10.5","jsdom":"^25.0.1","tsup":"^8.3.5","typescript":"^5.7.3","vite":"^5.4.11","vitest":"^2.1.8"},"repository":{"type":"git","url":"git+https://github.com/dmytromykhailiuk/typed-local-storage.git"},"bugs":{"url":"https://github.com/dmytromykhailiuk/typed-local-storage/issues"},"homepage":"https://dmytromykhailiuk.github.io/typed-local-storage/","gitHead":"8cf39dae53b693aa29278f36a97f30f053991d72","_id":"@dmytromykhailiuk/typed-local-storage@1.0.1","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-3uzfptof1KBBN/dOkGNuNgQ3U60o20yTPUWFQ2gQq+g5RZHpSy/a2pDFr/XD/4EVq+sWZjHj9ynt4/mOLJYMTQ==","shasum":"2d6e4b34d63dfbb2e51de62bf07ee2d96d8638a5","tarball":"https://registry.npmjs.org/@dmytromykhailiuk/typed-local-storage/-/typed-local-storage-1.0.1.tgz","fileCount":9,"unpackedSize":50169,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDtVNI58XJk8F2cpfyMUCPCFjQzDHCs7pKVhAXCDUbMcwIgFS9ueZ1pf9oRa88x8q8gVGvUxyyE+8eKteHEAw2eg7c="}]},"_npmUser":{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"},"directories":{},"maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/typed-local-storage_1.0.1_1786639380121_0.5817712346252795"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-25T15:20:17.869Z","modified":"2026-08-13T16:43:00.534Z","1.0.0":"2026-07-25T15:20:18.116Z","1.0.1":"2026-08-13T16:43:00.285Z"},"bugs":{"url":"https://github.com/dmytromykhailiuk/typed-local-storage/issues"},"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","homepage":"https://dmytromykhailiuk.github.io/typed-local-storage/","keywords":["localstorage","local-storage","storage","web-storage","typed","typescript","type-safe","persistence","cross-tab","storage-event","ssr","ssr-safe","zero-dependencies"],"repository":{"type":"git","url":"git+https://github.com/dmytromykhailiuk/typed-local-storage.git"},"description":"Typed localStorage with initial values, groups and cross-tab subscriptions. SSR-safe, dependency-free, a few hundred bytes.","maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"readme":"# @dmytromykhailiuk/typed-local-storage\n\nTyped `localStorage` with initial values, groups and cross-tab subscriptions.\nSSR-safe, dependency-free, a few hundred bytes.\n\n> **Full documentation:** open [Docs](https://dmytromykhailiuk.github.io/typed-local-storage/) in a\n> browser — every option, with examples, a table of contents and cross-links. This README is the\n> short form.\n\n> ⚠️ **The rule that makes it work:** never touch the raw `localStorage` object once its keys\n> are owned by storages — and **never call `localStorage.clear()`**. A direct `setItem` bypasses\n> typing, serialization and this tab's subscribers; `localStorage.clear()` levels the whole\n> origin, including keys that had to survive. Every operation has a safe counterpart:\n> `set`/`update` to write, `storage.clear()` to reset one storage, `group.clear()` to reset a\n> related slice.\n\nBuilt for apps that keep **a lot** in `localStorage` — settings, drafts, filters, tokens,\nper-feature caches. At that scale two things start to hurt: **typing**, because every read is an\nuntyped string you have to parse and trust; and **management**, because clearing state means\neither hunting down keys one by one or reaching for `localStorage.clear()` — which wipes the\nwhole origin, including what had to survive.\n\nThis library answers both. Each storage is declared **once**, owns **one key**, and carries its\nvalue type in its signature — no string keys scattered around the codebase, no\n`JSON.parse(localStorage.getItem(...) ?? \"null\")` ceremony, no silent crashes on corrupted data.\nAnd [groups](#groups) make cleanup targeted: related storages are cleared together, in one call,\neach by its own rules — everything else stays untouched.\n\n## Install\n\n```sh\nnpm i @dmytromykhailiuk/typed-local-storage\n```\n\n## Quick start\n\n```ts\nimport { createLocalStorage } from \"@dmytromykhailiuk/typed-local-storage\";\n\nconst settings = createLocalStorage(\"app:settings\", {\n  initialValue: { theme: \"dark\", fontSize: 14 },\n});\n\nsettings.get();                              // { theme: \"dark\", fontSize: 14 } — typed\nsettings.set({ theme: \"light\", fontSize: 16 });\nsettings.update((s) => ({ ...s, fontSize: s.fontSize + 1 }));\nsettings.clear();                            // back to the initial value\n\nconst unsubscribe = settings.subscribe((value) => {\n  // fires on every write through this instance — and when another tab writes the key\n});\n```\n\n- The value type is inferred from `initialValue` (or passed explicitly:\n  `createLocalStorage<Session>(\"app:session\")`).\n- With an `initialValue`, `get()` returns `T`; without one it returns `T | undefined` — the\n  types follow.\n- The initial value is written to storage at creation **only when the key is absent** — a stored\n  `0`, `false` or `\"\"` is a value, not an absence, and is never overwritten.\n\n## API\n\n```ts\nconst storage = createLocalStorage<T>(key, {\n  initialValue?: T;         // seeded when absent; restored by clear(); fallback for get()\n  isString?: boolean;       // store raw, without JSON (inferred for string initial values)\n  groups?: LocalStoragesGroup[];\n  onParseError?: (error, raw) => void;  // corrupted JSON hook; defaults to console.warn\n});\n\nstorage.get();              // T (with initialValue) or T | undefined\nstorage.set(value);\nstorage.update((v) => next);\nstorage.clear();            // reset to initialValue, or remove the key when there is none\nstorage.hasValue();         // does the key exist right now?\nstorage.subscribe(fn);      // local writes + other tabs' writes; returns unsubscribe\nstorage.key;                // the underlying key\nstorage.initialValue;\n```\n\nRegistering the same key twice throws — two storages writing one key with different types is a\nbug worth failing loudly on.\n\n## Safety\n\n- **Corrupted data never throws.** If the stored string is not valid JSON, `get()` reports it\n  through `onParseError` and falls back to `initialValue` (or `undefined`).\n- **SSR-safe.** Where `localStorage` is missing or throws (Node, sandboxed iframes, disabled\n  cookies), the same API runs against a shared in-memory fallback — pages render, nothing\n  persists, no guards needed in your code.\n- **String mode.** With `isString` (inferred when `initialValue` is a string) values are stored\n  raw — `\"dark\"`, not `\"\\\"dark\\\"\"` — which keeps keys readable and compatible with code that\n  wrote them before this library.\n\n## Cross-tab subscriptions\n\n`subscribe` listens to writes made through the instance **and** to the browser's `storage`\nevent, so a change made in another tab lands in the same callback:\n\n```ts\nconst theme = createLocalStorage(\"app:theme\", { initialValue: \"dark\" });\ntheme.subscribe((value) => document.body.dataset.theme = value);\n// another tab: theme.set(\"light\")  →  this tab's callback fires with \"light\"\n```\n\nThe `storage` event only fires in *other* tabs, so a write is delivered exactly once everywhere.\n\n## Groups\n\nWhen an app stores many keys, \"clear the user's data\" has two bad answers:\n`localStorage.clear()`, which levels the whole origin — theme, language, consent flags,\neverything that should have survived — and clearing keys one by one, a list that silently drifts\nout of date every time a feature adds a key. A group is the middle ground: storages that belong\ntogether are declared together, and reset together with one call — nothing outside the group is\ntouched. The classic case — \"clear everything user-scoped on logout\":\n\n```ts\nimport { createLocalStorage, createLocalStoragesGroup } from \"@dmytromykhailiuk/typed-local-storage\";\n\nconst userScoped = createLocalStoragesGroup(\"app:user-scoped\");\n\nconst session = createLocalStorage<Session>(\"app:session\", { groups: [userScoped] });\nconst drafts = createLocalStorage(\"app:drafts\", { initialValue: [], groups: [userScoped] });\n\nuserScoped.clear(); // session removed, drafts reset to []\n```\n\nThe group persists its member list (key + initial value) in storage under its own name, so\n`clear()` also covers keys registered by **previous sessions** — code paths that didn't run\nthis time can't leak stale data. Members with live instances are cleared through them, so their\nsubscribers are notified.\n\n## TypeScript\n\n```ts\nconst counter = createLocalStorage(\"counter\", { initialValue: 0 });\ncounter.get();                 // number — no undefined\ncounter.update((v) => v + 1);  // v: number\n\nconst session = createLocalStorage<Session>(\"session\");\nsession.get();                 // Session | undefined\nsession.update((v) => v ?? emptySession);  // v: Session | undefined\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}