{"_id":"@emdzej/bass-client","name":"@emdzej/bass-client","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@emdzej/bass-client","version":"0.1.0","description":"Client library for the bass backendless app state sync service","license":"MIT","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"devDependencies":{"@types/node":"^20.16.5","jsdom":"^25.0.1","typescript":"^5.6.3","vitest":"^2.1.4"},"publishConfig":{"access":"public"},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test":"vitest run --passWithNoTests","clean":"rm -rf dist .turbo"},"_id":"@emdzej/bass-client@0.1.0","_integrity":"sha512-YbFjkY6CVETOdnnp75afnWIrjEdzUm6P3NeVWl7/Mr34UKl2PzeUpgyIf1ahHZ+JYxB+PdxhOZNJWGhcjZITqg==","_resolved":"/private/var/folders/7h/x_w_580x4s9dq3sq11tpvzkwy3nbj8/T/b75f303e539c1d50b4ed9b15b49c9340/emdzej-bass-client-0.1.0.tgz","_from":"file:emdzej-bass-client-0.1.0.tgz","_nodeVersion":"22.22.1","_npmVersion":"11.16.0","dist":{"integrity":"sha512-YbFjkY6CVETOdnnp75afnWIrjEdzUm6P3NeVWl7/Mr34UKl2PzeUpgyIf1ahHZ+JYxB+PdxhOZNJWGhcjZITqg==","shasum":"677abdcc3c1efa2fa550db750fd5f2da35139200","tarball":"https://registry.npmjs.org/@emdzej/bass-client/-/bass-client-0.1.0.tgz","fileCount":42,"unpackedSize":79697,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDP6wVMWCL/l1kcoCNE4GcgvZOHD25MLU+XZ530Dl5d/wIgKTY6UReQEznZBMXlOkQtI//lL+QOg/f3a5tkxgAC4Ak="}]},"_npmUser":{"name":"emdzej","email":"michal@jaskolski.pro"},"directories":{},"maintainers":[{"name":"emdzej","email":"michal@jaskolski.pro"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/bass-client_0.1.0_1782726553302_0.28289909968842464"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-29T09:49:13.160Z","0.1.0":"2026-06-29T09:49:13.426Z","modified":"2026-06-29T09:49:13.647Z"},"maintainers":[{"name":"emdzej","email":"michal@jaskolski.pro"}],"description":"Client library for the bass backendless app state sync service","license":"MIT","readme":"# @emdzej/bass-client\n\nClient library for **[bass](https://github.com/emdzej/bass)** — backendless\napp state synchronization.\n\nDrop-in `localStorage` sync across a user's devices for apps that\nintentionally have no backend. Works fully offline / unpaired (no-op),\nopts into sync when the user pairs once via OIDC.\n\n```sh\npnpm add @emdzej/bass-client\n```\n\n## Quick start\n\n```ts\nimport { createBassClient } from '@emdzej/bass-client';\n\nconst bass = createBassClient({\n  serviceUrl: 'https://bass.example.com',\n  appId: 'my-app',\n  keys: ['myapp-*'],           // optional, default ['*']\n});\n\n// One-time pairing (call from a \"sync settings\" button)\nawait bass.pair({\n  redirectUri: location.origin + '/sync-cb',\n  mode: 'redirect',            // or 'popup'\n});\n\n// In your /sync-cb route, finish the pairing\nbass.completePairingFromUrl();\n\n// On boot, hydrate local cache from server before mounting UI\nif (bass.isPaired()) {\n  await bass.hydrate({ timeoutMs: 2000 });\n}\n\n// Pick one (or both):\n\n// (a) transparent proxy — keep using window.localStorage\nbass.attachLocalStorageProxy();\n\n// (b) manual API\nawait bass.set('myapp-theme', 'dark');\nconst v = bass.get('myapp-theme');\nconst unsub = bass.subscribe('myapp-theme', (v) => render(v));\n\n// Start the WS notification channel for live updates from other devices\nawait bass.startNotifications();\n```\n\n## Why two APIs?\n\n- **Manual** — explicit, fits state libraries (Redux, Zustand, Pinia, Svelte stores).\n- **Proxy** — zero-touch. Existing `localStorage.setItem(...)` calls just work.\n\nPick whichever suits your codebase. They can coexist.\n\n## Reading\n\nReads always come from `localStorage` synchronously. The library never\nblocks a read on the network — `bass.get(key)` and `localStorage.getItem(key)`\nbehave identically.\n\n## Writing\n\nWrites go to:\n1. `localStorage` immediately (so the next read sees them).\n2. An outbox keyed by sync key (latest write per key wins — chatty apps\n   coalesce naturally).\n\nThe outbox drains on a debounce timer, on reconnect, and on `bass.flush()`.\nEntries survive a tab close (they live in `localStorage` themselves).\n\n## Offline / unpaired\n\nIf `bass.isPaired()` is `false`, the proxy is a passthrough and the manual\nAPI just hits `localStorage`. Apps don't need to branch on auth state —\nthey keep working with or without sync.\n\n## Pairing modes\n\n| Mode | Behaviour | When to use |\n|---|---|---|\n| `redirect` | Whole-page navigation to the IdP, redirect back to your `/sync-cb`. | Default. Mobile-safe, no popup blockers. |\n| `popup` | `window.open` to the IdP. Tokens postMessage'd back to opener. | Desktop UX. Falls back to redirect when popups are blocked. |\n\nThe `/sync-cb` route in your app calls `bass.completePairingFromUrl()` —\nthe same line handles both modes.\n\n## Full reference\n\nSee [the bass docs](https://github.com/emdzej/bass/blob/main/docs/API.md)\nfor the underlying REST + WS protocol, and\n[`SPEC.md`](https://github.com/emdzej/bass/blob/main/SPEC.md) for the\ndesign rationale.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-9fd9e847d845ba7f01f8627e22639ed7"}