{"_id":"@assistshub/ah-js-utils","_rev":"3-ee4d073d7ddbaa25a5daab1789aef0e9","name":"@assistshub/ah-js-utils","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.0":{"name":"@assistshub/ah-js-utils","version":"0.1.0","keywords":["react","hooks","localStorage","sessionStorage","cookies","assistshub"],"author":{"url":"https://assistshub.com","name":"Bielorusov","email":"assistshub@gmail.com"},"license":"MIT","_id":"@assistshub/ah-js-utils@0.1.0","maintainers":[{"name":"assistshub","email":"assistshub@gmail.com"}],"homepage":"https://github.com/bielorusov/ah-js-utils#readme","bugs":{"url":"https://github.com/bielorusov/ah-js-utils/issues"},"dist":{"shasum":"262756454c950993db374a01d9d8e0476bc1cb53","tarball":"https://registry.npmjs.org/@assistshub/ah-js-utils/-/ah-js-utils-0.1.0.tgz","fileCount":9,"integrity":"sha512-Ec87mxxNq0ntF6Izx9nyoe0sV/rXu560qQVLx9KGWvrwY83haQUiB27+4WWbVHeDZxnHAgLIaF4MmE+ZsobpiA==","signatures":[{"sig":"MEYCIQCKa7691+v6vuviFzu7b7kBsmD1M0tNItZMs0QaYhQFIAIhALDa4OEnvAsIceeOJpI5hT0Si8sK9DGBEjyGNXwwFD4G","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":36230},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"b0e8158fdcfeee331bd7ab40ad1c4fe53b93bc15","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build"},"_npmUser":{"name":"assistshub","email":"assistshub@gmail.com"},"repository":{"url":"git+https://github.com/bielorusov/ah-js-utils.git","type":"git"},"_npmVersion":"11.7.0","description":"React storage hooks and small JS utilities for AssistsHub apps (localStorage, sessionStorage, cookies)","directories":{},"sideEffects":false,"_nodeVersion":"24.12.0","dependencies":{"js-cookie":"^3.0.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.0","jsdom":"^26.1.0","react":"^19.1.0","vitest":"^3.2.4","react-dom":"^19.1.0","typescript":"^5.8.3","@types/react":"^19.1.8","@types/js-cookie":"^3.0.6","@types/react-dom":"^19.1.6","@vitest/coverage-v8":"^3.2.4","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.0","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ah-js-utils_0.1.0_1784030029847_0.514119736647829","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@assistshub/ah-js-utils","version":"0.1.2","keywords":["react","hooks","localStorage","sessionStorage","cookies","indexedDB","IndexedDB","assistshub"],"author":{"url":"https://assistshub.com","name":"Bielorusov","email":"assistshub@gmail.com"},"license":"MIT","_id":"@assistshub/ah-js-utils@0.1.2","maintainers":[{"name":"assistshub","email":"assistshub@gmail.com"}],"homepage":"https://github.com/bielorusov/packages/blob/main/npm/ah-js-utils#readme","bugs":{"url":"https://github.com/bielorusov/packages/blob/main/npm/ah-js-utils/issues"},"dist":{"shasum":"4c9c8851092d1193148bb54c355167f418b22090","tarball":"https://registry.npmjs.org/@assistshub/ah-js-utils/-/ah-js-utils-0.1.2.tgz","fileCount":9,"integrity":"sha512-BZ/HqeL8pcLeZRyv9OPB1bNZOw22xFKiiLd+fBQo6/XEpV0of65Fx5uPABTe/UnbGtqtcmJC7Y61jOVUibw4+Q==","signatures":[{"sig":"MEQCIA+bee7IFc1siMcS1vV2NVARF+e0rWW8VPRC2g1cau+CAiAZ+fX8HTWjuFLnWWbEl/ZkmKjppyCoXxWmiOHlcGfSUA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIGhZdAN2ckWXBGJ7oCge7fM/OlsjE+HwrworPP6DfvvKAiEA5+O88u2l0+91eyYy2XPiSHI4ylkUlzB/6dzdZZQ8+hg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":192651},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"bad59d63f2fbe9ddfd043aedff5dd99866cbb02f","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build"},"_npmUser":{"name":"assistshub","email":"assistshub@gmail.com"},"repository":{"url":"https://github.com/bielorusov/packages/blob/main/npm/ah-js-utils.git","type":"git"},"_npmVersion":"11.7.0","description":"React storage hooks and small JS utilities for AssistsHub apps (localStorage, sessionStorage, cookies, IndexedDB)","directories":{},"sideEffects":false,"_nodeVersion":"24.12.0","dependencies":{"js-cookie":"^3.0.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.0","jsdom":"^26.1.0","react":"^19.1.0","vitest":"^3.2.4","react-dom":"^19.1.0","typescript":"^5.8.3","@types/react":"^19.1.8","fake-indexeddb":"^6.2.5","@types/js-cookie":"^3.0.6","@types/react-dom":"^19.1.6","@vitest/coverage-v8":"^3.2.4","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.0","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ah-js-utils_0.1.2_1789900048186_0.5172264863433371","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"_id":"@assistshub/ah-js-utils@0.1.3","bugs":{"url":"https://github.com/bielorusov/packages/blob/main/npm/ah-js-utils/issues"},"dist":{"shasum":"d9afe38ee016ca153e706ad639910b32465acff8","tarball":"https://registry.npmjs.org/@assistshub/ah-js-utils/-/ah-js-utils-0.1.3.tgz","fileCount":9,"integrity":"sha512-qmh0SJLPCWtvrPPI1tegdGdfrHbK/kmwD+DWVa9n+6GeHRs6Os4xw2/g4SZgZ5kUTzfqZPnv0VNsV1Mrwb7agA==","signatures":[{"sig":"MEQCIGjgxphZ/eTGB9O7/WHUq7+wttRs7p1Y13H3rXBFVDg9AiBZjOfHbdL9tpsZypI4qv0avQlhiQPFed+9BqVnDbiDcw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIGfvt7gLZPtnDDlaJtJPY7N0SJL4runj5aMz1Zo5x/qQAiEA49aZK6U1QIG4iwP/GfBJhq5PlisNSFzJ2QhR0rMuuzE="}],"unpackedSize":192673},"main":"./dist/index.cjs","name":"@assistshub/ah-js-utils","type":"module","types":"./dist/index.d.ts","author":{"url":"https://assistshub.com","name":"Bielorusov","email":"assistshub@gmail.com"},"module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"4941cec06b30e9da7f5462fb0413cbab310634a0","license":"MIT","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build"},"version":"0.1.3","_npmUser":{"name":"assistshub","email":"assistshub@gmail.com"},"homepage":"https://github.com/bielorusov/packages/blob/main/npm/ah-js-utils#readme","keywords":["react","hooks","localStorage","sessionStorage","cookies","indexedDB","IndexedDB","assistshub"],"repository":{"url":"https://github.com/bielorusov/packages/blob/main/npm/ah-js-utils.git","type":"git"},"_npmVersion":"11.7.0","description":"React storage hooks and small JS utilities for AssistsHub apps (localStorage, sessionStorage, cookies, IndexedDB)","directories":{},"maintainers":[{"name":"assistshub","email":"assistshub@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.12.0","dependencies":{"js-cookie":"^3.0.8"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","vite":"^8.3.0","jsdom":"^29.1.1","react":"^19.3.0","vitest":"^5.0.1","react-dom":"^19.3.0","typescript":"^5.9.3","@types/react":"^19.3.0","fake-indexeddb":"^6.2.5","@types/js-cookie":"^3.0.6","@types/react-dom":"^19.3.0","@vitest/coverage-v8":"^5.0.1","@testing-library/dom":"^10.4.2","@testing-library/react":"^16.3.3","@testing-library/jest-dom":"^7.0.1"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ah-js-utils_0.1.3_1789901040189_0.3641821169124291"}}},"time":{"created":"2026-07-14T11:53:49.667Z","modified":"2026-09-20T10:44:00.494Z","0.1.0":"2026-07-14T11:53:49.985Z","0.1.2":"2026-09-20T10:27:28.264Z","0.1.3":"2026-09-20T10:44:00.301Z"},"bugs":{"url":"https://github.com/bielorusov/packages/blob/main/npm/ah-js-utils/issues"},"author":{"url":"https://assistshub.com","name":"Bielorusov","email":"assistshub@gmail.com"},"license":"MIT","homepage":"https://github.com/bielorusov/packages/blob/main/npm/ah-js-utils#readme","keywords":["react","hooks","localStorage","sessionStorage","cookies","indexedDB","IndexedDB","assistshub"],"repository":{"url":"https://github.com/bielorusov/packages/blob/main/npm/ah-js-utils.git","type":"git"},"description":"React storage hooks and small JS utilities for AssistsHub apps (localStorage, sessionStorage, cookies, IndexedDB)","maintainers":[{"name":"assistshub","email":"assistshub@gmail.com"}],"readme":"# ah-js-utils\n\nReact storage hooks and small JSON helpers for AssistsHub (and any React) apps: **localStorage**, **sessionStorage**, **cookies**, and **IndexedDB**, with a shared `{ value, setValue, reset }` API.\n\nPublished as `@assistshub/ah-js-utils`.\n\n## Install\n\n```bash\nnpm install @assistshub/ah-js-utils\n```\n\n### Peer dependencies\n\n| Package     | Version |\n|-------------|---------|\n| `react`     | `>=18`  |\n| `react-dom` | `>=18`  |\n\nCompatible with React 18 and React 19.\n\n`js-cookie` is a runtime dependency of this package (used by `useCookieStorage`). IndexedDB helpers use the browser `indexedDB` and Web Crypto APIs and need no extra runtime dependency.\n\n## Quick start\n\n```tsx\nimport {\n  useLocalStorage,\n  useSessionStorage,\n  useCookieStorage,\n  useIndexedDBStorage,\n} from '@assistshub/ah-js-utils'\n\nfunction ThemeToggle() {\n  const { value: theme, setValue, reset } = useLocalStorage<'dark' | 'light'>('theme', 'dark')\n\n  return (\n    <div>\n      <p>Theme: {theme}</p>\n      <button type=\"button\" onClick={() => setValue(t => (t === 'dark' ? 'light' : 'dark'))}>\n        Toggle\n      </button>\n      <button type=\"button\" onClick={() => reset()}>\n        Reset\n      </button>\n    </div>\n  )\n}\n\nfunction SidebarState() {\n  const { value: open, setValue } = useSessionStorage('sidebar_open', true)\n  return (\n    <button type=\"button\" onClick={() => setValue(v => !v)}>\n      Sidebar {open ? 'open' : 'closed'}\n    </button>\n  )\n}\n\nfunction SessionCookie() {\n  const { value, setValue, reset } = useCookieStorage(\n    '__APP_SESSION__',\n    { token: null as string | null },\n    { secure: true, expires: 365, path: '/' },\n  )\n\n  return (\n    <div>\n      <pre>{JSON.stringify(value)}</pre>\n      <button type=\"button\" onClick={() => setValue({ token: 'demo' })}>\n        Sign in\n      </button>\n      <button type=\"button\" onClick={() => reset()}>\n        Sign out\n      </button>\n    </div>\n  )\n}\n\nfunction Watchlist() {\n  const { value: pairs, setValue, reset, ready } = useIndexedDBStorage<string[]>(\n    'watchlist',\n    [],\n    { dbName: 'my-app' },\n  )\n\n  if (!ready) return <p>Loading…</p>\n\n  return (\n    <div>\n      <p>{pairs.length} pairs</p>\n      <button type=\"button\" onClick={() => setValue(prev => [...prev, 'BTCUSDT'])}>\n        Add\n      </button>\n      <button type=\"button\" onClick={() => reset()}>\n        Clear\n      </button>\n    </div>\n  )\n}\n```\n\n## API\n\n`useLocalStorage`, `useSessionStorage`, and `useCookieStorage` share this return shape:\n\n```ts\n{\n  value: T\n  setValue: (next: T | ((prev: T) => T)) => void\n  reset: () => void\n}\n```\n\n- Values are **JSON-encoded** in storage.\n- Missing / empty / invalid JSON falls back to `initialValue`.\n- `typeof window === 'undefined'` returns `initialValue` and skips storage I/O (SSR-safe reads).\n\n`useIndexedDBStorage` adds `ready: boolean` because IndexedDB reads are asynchronous. It stores structured-clone values (not JSON strings), so it is a better fit for large objects than cookies or `localStorage`. It works standalone or inside `IndexedDBProvider`.\n\n### `useLocalStorage(key, initialValue)`\n\nPersists to `window.localStorage`.\n\n```tsx\nconst { value, setValue, reset } = useLocalStorage('user_prefs', {\n  theme: 'dark',\n  lang: 'en',\n})\n\nsetValue(prev => ({ ...prev, theme: 'light' }))\nreset() // restores initialValue and removes the key\n```\n\n### `useSessionStorage(key, initialValue)`\n\nPersists to `window.sessionStorage` (cleared when the tab/session ends).\n\n```tsx\nconst { value: filter, setValue } = useSessionStorage<string>('offers_filter', '')\nsetValue('graphql')\n```\n\n### `useCookieStorage(key, initialValue, options?)`\n\nPersists via [`js-cookie`](https://github.com/js-cookie/js-cookie). Default options:\n\n```ts\n{ secure: true, expires: 365, path: '/' }\n```\n\n```tsx\nimport type { CookieOptions } from '@assistshub/ah-js-utils'\n\nconst cookieOpts: CookieOptions = { secure: true, path: '/', expires: 30 }\n\nconst { value, setValue, reset } = useCookieStorage('prefs', { n: 0 }, cookieOpts)\n```\n\n`reset()` removes the cookie using `options.path` (or `'/'` if omitted).\n\n### `useIndexedDBStorage(key, initialValue, options?)`\n\nPersists to IndexedDB as `{ id, value }` records. First paint uses `getInitial?.() ?? initialValue`; `ready` becomes `true` after the initial read (or immediately on SSR / when IndexedDB is missing).\n\nDefault options:\n\n```ts\n{ dbName: 'ah-js-utils', storeName: 'kv', version: 1, sync: true }\n```\n\nGive each app its own `dbName` so databases do not collide.\n\n```tsx\nimport type { IndexedDBStorageOptions } from '@assistshub/ah-js-utils'\n\nconst idbOpts: IndexedDBStorageOptions<string[]> = { dbName: 'my-app' }\n\nconst { value, setValue, reset, ready } = useIndexedDBStorage('watchlist', [], idbOpts)\n\nsetValue(prev => [...prev, 'ETHUSDT'])\nreset() // restores initialValue and deletes the key\n```\n\nOptional `options`:\n\n- `dbName` / `storeName` / `version` — IndexedDB identity. Changing `storeName` at the same `version` will not create a new store.\n- `sync` — cross-tab `BroadcastChannel` updates (`${dbName}-kv`). Default `true`.\n- `prepare` — async callback after the DB opens, before the initial get (e.g. migrate from `sessionStorage`).\n- `getInitial` — synchronous snapshot used on mount. Return a value to skip the IndexedDB get; return `undefined` for a cold start.\n\nLow-level helpers (`openIndexedDB`, `indexedDBGet`, `indexedDBPut`, `indexedDBDelete`, `indexedDBGetAll`, `indexedDBClear`, `closeIndexedDB`, `deleteIndexedDB`) are exported for non-hook use.\n\n### `IndexedDBProvider` / `useIndexedDB()`\n\nOptional app-level wrapper. Use it when several hooks should share one database, or when you need a password-protected secure store (AES-GCM, PBKDF2).\n\n`encryption.verifier` must be unique per app (it is encrypted and used to check the password). Do not reuse another app’s verifier string.\n\n```tsx\nimport { IndexedDBProvider, useIndexedDB } from '@assistshub/ah-js-utils'\n\nfunction Root() {\n  return (\n    <IndexedDBProvider\n      dbName=\"assistshub\"\n      encryption={{ verifier: 'assistshub-idb-verifier-v1' }}\n    >\n      <Vault />\n    </IndexedDBProvider>\n  )\n}\n\nfunction Vault() {\n  const { ready, isUnlocked, unlock, lock, putSecure, getSecure, deleteSecure } = useIndexedDB()\n\n  if (!ready) return null\n  if (!isUnlocked) {\n    return (\n      <button type=\"button\" onClick={() => void unlock('correct-horse')}>\n        Unlock\n      </button>\n    )\n  }\n\n  return (\n    <div>\n      <button\n        type=\"button\"\n        onClick={() => void putSecure({ id: 'card', pan: '4111', brand: 'visa' }, ['pan'])}\n      >\n        Save card\n      </button>\n      <button type=\"button\" onClick={() => void getSecure('card')}>\n        Load\n      </button>\n      <button type=\"button\" onClick={() => void deleteSecure('card')}>\n        Delete\n      </button>\n      <button type=\"button\" onClick={() => lock()}>\n        Lock\n      </button>\n    </div>\n  )\n}\n```\n\n- `useIndexedDB()` throws if it is used outside `IndexedDBProvider`.\n- Omit `encryption` for plaintext KV only. `unlock` / `putSecure` / `getSecure` / `deleteSecure` then throw `IndexedDBEncryptionDisabledError`.\n- `useIndexedDBStorage` does **not** require the provider. Inside a provider it inherits `dbName` and uses the shared get/put/delete.\n\n`putSecure(record, encryptFields?)`: omit `encryptFields` to encrypt every field except `id`. Pass a list to encrypt only those fields; the rest stay in `plaintext` on the stored record.\n\n### `encodeJson` / `decodeJson`\n\nSmall helpers used by the JSON storage hooks; exported for reuse:\n\n```ts\nimport { encodeJson, decodeJson } from '@assistshub/ah-js-utils'\n\nconst raw = encodeJson({ a: 1 }) // '{\"a\":1}'\nconst value = decodeJson(raw, { a: 0 }) // { a: 1 }\nconst fallback = decodeJson('{broken', { a: 0 }) // { a: 0 }\n```\n\n## Scripts\n\n```bash\nnpm test\nnpm run test:coverage\nnpm run build\nnpm run typecheck\n```\n\n## Publishing (manual)\n\nThis package is prepared for a public npm release under the `@assistshub` scope. Suggested steps:\n\n1. Create GitHub repo: [https://github.com/bielorusov/ah-js-utils](https://github.com/bielorusov/ah-js-utils)\n2. Push the contents of this folder (or split from the monorepo)\n3. `npm login`\n4. `npm publish` (uses `\"publishConfig\": { \"access\": \"public\" }`)\n\nUntil published, consumers in this monorepo can install via:\n\n```json\n\"@assistshub/ah-js-utils\": \"file:../ah-js-utils\"\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}