{"_id":"@andrewpopov/fetch-client-kit","name":"@andrewpopov/fetch-client-kit","dist-tags":{"latest":"0.2.0"},"versions":{"0.2.0":{"name":"@andrewpopov/fetch-client-kit","version":"0.2.0","description":"Framework-agnostic browser fetch client: a single-flight 401 -> refresh -> retry transport with pluggable auth (cookie / bearer / csrf).","author":{"name":"Andrew Popov"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/andrewpopov/fetch-client-kit.git"},"homepage":"https://github.com/andrewpopov/fetch-client-kit#readme","bugs":{"url":"https://github.com/andrewpopov/fetch-client-kit/issues"},"keywords":["fetch","http-client","auth","token-refresh","401","retry","single-flight","bearer","csrf"],"publishConfig":{"access":"public"},"main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"engines":{"node":">=20"},"scripts":{"build":"tsc","prepublishOnly":"npm run build","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","verify:pack":"node scripts/verify-pack.mjs"},"devDependencies":{"@types/node":"^20.14.0","typescript":"^5.5.0","vitest":"^2.1.0"},"gitHead":"742d06a2b4f16f1f0b37f30bc3f819cb8824b9c9","_id":"@andrewpopov/fetch-client-kit@0.2.0","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-1+ymCq9SdGt22JAW63lSeXBfn8XAWdJeUff8RGRTLOfoA/8T46del0BFySo/7edCwvMMERI1T/xPNNtiVPhxiQ==","shasum":"fd8900fae9a6cff12ea5b55b4c51a941445ab325","tarball":"https://registry.npmjs.org/@andrewpopov/fetch-client-kit/-/fetch-client-kit-0.2.0.tgz","fileCount":5,"unpackedSize":14925,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHT2nuN0VPhJIYUtcipvSdMVzzZLuWiYIlCGSWwwcoQ2AiEAk5NKcSIja9EPjaQ5oxahMZGznO8f3jMLR4AkLx7LKJ0="}]},"_npmUser":{"name":"a_popov","email":"andrewvpopov@gmail.com"},"directories":{},"maintainers":[{"name":"a_popov","email":"andrewvpopov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/fetch-client-kit_0.2.0_1783721326785_0.37499536462049976"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-10T22:08:46.651Z","0.2.0":"2026-07-10T22:08:46.934Z","modified":"2026-07-10T22:08:47.135Z"},"maintainers":[{"name":"a_popov","email":"andrewvpopov@gmail.com"}],"description":"Framework-agnostic browser fetch client: a single-flight 401 -> refresh -> retry transport with pluggable auth (cookie / bearer / csrf).","homepage":"https://github.com/andrewpopov/fetch-client-kit#readme","keywords":["fetch","http-client","auth","token-refresh","401","retry","single-flight","bearer","csrf"],"repository":{"type":"git","url":"git+https://github.com/andrewpopov/fetch-client-kit.git"},"author":{"name":"Andrew Popov"},"bugs":{"url":"https://github.com/andrewpopov/fetch-client-kit/issues"},"license":"MIT","readme":"# @andrewpopov/fetch-client-kit\n\nFramework-agnostic browser fetch client: on a `401`, it refreshes auth **once**\nand retries — deduplicating concurrent refreshes so N overlapping `401`s trigger\nexactly **one** refresh. How auth attaches to a request (session cookie, bearer\ntoken, CSRF header) is the one pluggable seam, an `AuthStrategy`. Zero runtime\ndependencies; the browser `fetch` is the only ambient requirement.\n\n## Install\n\n```bash\nnpm install github:andrewpopov/fetch-client-kit#v0.2.0\n```\n\n## Usage\n\n```ts\nimport { createFetchClient, cookieAuth } from '@andrewpopov/fetch-client-kit';\n\nconst api = createFetchClient({ baseUrl: '/api', auth: cookieAuth() });\nconst user = await api.request<User>('/me');\n```\n\n## Auth strategies\n\n| Strategy | Attaches | For |\n|---|---|---|\n| `cookieAuth()` | `credentials: 'include'` | session cookies |\n| `bearerAuth({ getAccessToken, onRefreshed })` | `Authorization: Bearer …` | a token kept in your own store |\n| `csrfAuth({ getCsrfToken })` | `x-csrf-token` header | CSRF double-submit |\n\nEvery built-in strategy accepts `refreshPath` (default `'/api/auth/refresh'`)\nand `credentials`; `csrfAuth` also accepts `headerName`. Each adds\n`Content-Type: application/json` unless the caller already set one or the body\nis a `FormData` — the browser must set that header itself to include the\nmultipart `boundary=`.\n\nThe token accessors are injected, so the package never owns where tokens live.\nWrite your own `AuthStrategy` for anything else — it is a two-method interface\n(`decorate` a request, `refresh`).\n\n## API\n\n`createFetchClient(options)` returns `{ request, refresh }`.\n\n| Option | Default | Meaning |\n|---|---|---|\n| `baseUrl` | required | prefixed to every request path |\n| `auth` | required | an `AuthStrategy` |\n| `fetcher` | global `fetch` | injected for tests |\n| `authPathPrefixes` | `['/api/auth/']` | paths (matched by prefix) whose `401`s never trigger a refresh — the auth endpoints themselves |\n| `parseError` | reads a JSON `{ error }` body, falls back to status text | turns a non-ok `Response` into the `Error` that `request` rejects with |\n| `onAuthFailure` | — | called once when a refresh fails on a retriable `401`, e.g. to clear auth state and redirect to login; the request still rejects with its own error |\n\n- `request<T>(path, init?)` — resolves with the parsed JSON body. `204` and\n  empty bodies resolve to `undefined`; non-ok responses reject with\n  `parseError`'s Error (the default attaches `.status`).\n- `refresh()` — force a refresh (e.g. on app focus); shares the same\n  single-flight promise as the `401` path.\n\n## The single-flight guarantee\n\nThe first `401` starts a refresh; every concurrent `401` awaits the **same**\npromise instead of firing its own. Auth-endpoint `401`s never trigger a refresh.\nA failed refresh does not retry — the original error surfaces.\n","readmeFilename":"README.md","_rev":"1-66dbe1456dcfc8cae97cb4179b0616d4"}