{"_id":"@buildwithdarsh/historyjs","name":"@buildwithdarsh/historyjs","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@buildwithdarsh/historyjs","version":"1.0.0","description":"A modern, typed, framework-agnostic wrapper over the History API — typed entries, guards, query helpers, route matching, link interception, and a virtual stack. The 2026 successor to history.js.","main":"dist/history.umd.js","module":"dist/history.esm.js","types":"dist/history.d.ts","unpkg":"dist/history.umd.js","jsdelivr":"dist/history.umd.js","exports":{".":{"types":"./dist/history.d.ts","import":"./dist/history.esm.js","require":"./dist/history.umd.js"}},"publishConfig":{"access":"public"},"scripts":{"build":"rollup -c && rm -rf example/dist && cp -r dist example/dist","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","dev":"rollup -c -w","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"keywords":["history","historyjs","pushState","popstate","router","spa","navigation","url","query-string","route-matcher","typescript"],"author":{"name":"Darsh Gupta"},"license":"MIT","type":"module","sideEffects":false,"devDependencies":{"@rollup/plugin-terser":"^1.0.0","@rollup/plugin-typescript":"^12.1.2","happy-dom":"^15.0.0","rollup":"^4.34.8","rollup-plugin-dts":"^6.1.1","tslib":"^2.8.1","typescript":"^5.7.3","vitest":"^2.1.0"},"_id":"@buildwithdarsh/historyjs@1.0.0","gitHead":"bd943ae4a47e1e32100f8ca9818328d03d329e21","_nodeVersion":"22.20.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-qYRG571i1ue8r9QKZ+nt+ml+3PzRuJT/t1O6s48ZEmwGhg9ZF10FRnAPesJU5K4r1hMAkPeeecaya1fENERFrw==","shasum":"edd4fc11df5fa199045f34cd396c54603a06bcf9","tarball":"https://registry.npmjs.org/@buildwithdarsh/historyjs/-/historyjs-1.0.0.tgz","fileCount":8,"unpackedSize":59158,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDnGgGAoDVbeOnqAkbcdCM24TlvEwuzOFNFgbo/+fhErQIgdCVzGiYQsibNMTx+BwHNwVEm52xp7TCLpAcZksCmN3s="}]},"_npmUser":{"name":"withdarsh","email":"withdarsh@gmail.com"},"directories":{},"maintainers":[{"name":"withdarsh","email":"withdarsh@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/historyjs_1.0.0_1779077146068_0.6044922239725468"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-18T04:05:45.872Z","1.0.0":"2026-05-18T04:05:46.220Z","modified":"2026-05-18T04:05:46.502Z"},"maintainers":[{"name":"withdarsh","email":"withdarsh@gmail.com"}],"description":"A modern, typed, framework-agnostic wrapper over the History API — typed entries, guards, query helpers, route matching, link interception, and a virtual stack. The 2026 successor to history.js.","keywords":["history","historyjs","pushState","popstate","router","spa","navigation","url","query-string","route-matcher","typescript"],"author":{"name":"Darsh Gupta"},"license":"MIT","readme":"# HistoryJS\n\nA modern, typed, framework-agnostic wrapper over the browser History API. The 2026 successor to [browserstate/history.js](https://github.com/browserstate/history.js/).\n\n[![npm](https://img.shields.io/badge/npm-v1.0.0-bf5af2)](https://www.npmjs.com/package/@buildwithdarsh/historyjs)\n[![bundle](https://img.shields.io/badge/gzipped-~3KB-2997ff)](#)\n[![license](https://img.shields.io/badge/license-MIT-30d158)](LICENSE)\n[![types](https://img.shields.io/badge/TypeScript-first-2997ff)](#)\n\nThe original `history.js` shipped in 2010 to paper over the HTML4/HTML5 split. Every browser worth supporting today implements `pushState`/`popstate` natively, so this rewrite drops the HTML4 fallback, the jQuery/MooTools/Prototype adapters, and the global `History` namespace — and gives you what apps actually want now: typed entries, async navigation guards, query helpers, route matching, link interception, and a virtual stack.\n\n## Install\n\n```bash\nnpm install @buildwithdarsh/historyjs\n# or\npnpm add @buildwithdarsh/historyjs\n# or\nyarn add @buildwithdarsh/historyjs\n```\n\nOr via CDN:\n\n```html\n<script src=\"https://unpkg.com/@buildwithdarsh/historyjs\"></script>\n```\n\n## Quick start\n\n```ts\nimport { getHistory } from '@buildwithdarsh/historyjs';\n\ntype AppState = { view: 'home' | 'profile'; userId?: string };\n\nconst history = getHistory<AppState>();\n\nhistory.subscribe((event) => {\n  console.log(event.type, '→', event.to.location.pathname, event.to.state);\n});\n\nawait history.push('/profile/42', { state: { view: 'profile', userId: '42' } });\nhistory.back();\n```\n\n## Why a rewrite?\n\n| Original `history.js` (2010)              | `@buildwithdarsh/historyjs` (2026) |\n| ----------------------------------------- | ----------------------------------- |\n| HTML4 hashchange fallback                 | Native History API only             |\n| jQuery / MooTools / Prototype adapters    | Zero dependencies                   |\n| Global `History` namespace, untyped state | TypeScript-first, generic state     |\n| Event-name strings (`statechange`)        | Typed `NavigationEvent` callbacks   |\n| No navigation guards                      | Async guards with cancellation      |\n| Manual query/hash building                | `setQuery`, `setHash`, `getQuery`   |\n| No routing primitives                     | Built-in pattern matcher            |\n| Manual `<a>` interception                 | `interceptLinks(history)`           |\n| Manual scroll handling                    | Built-in scroll restoration         |\n\n## Core concepts\n\n### Entries\n\nEvery navigation produces a typed `HistoryEntry<TState>`:\n\n```ts\n{\n  id: string;            // stable, survives reload\n  state: TState | null;  // your typed state\n  title: string;\n  location: { pathname, search, hash, href, origin };\n  index: number;         // monotonic\n  timestamp: number;     // Date.now() at navigation\n}\n```\n\n### Navigation\n\n```ts\nawait history.push('/path', { state: {...}, title: 'New title' });\nawait history.replace('/path');\nhistory.back();       // or history.back(2)\nhistory.forward();\nhistory.go(-3);\nhistory.reload();\n```\n\n`push` and `replace` are async because guards may be async. They resolve to `true` if the navigation completed, `false` if it was cancelled.\n\n### Listeners\n\n```ts\nconst unsubscribe = history.subscribe((event) => {\n  // event.type:      'push' | 'replace' | 'pop'\n  // event.direction: 'forward' | 'backward' | 'none'\n  // event.from / event.to: HistoryEntry\n  // event.isPopState: true if triggered by browser back/forward\n});\n\nunsubscribe();\n```\n\n### Guards\n\nGuards run before every navigation. Return `false` to cancel.\n\n```ts\nhistory.addGuard(async (event) => {\n  if (event.to.location.pathname.startsWith('/admin')) {\n    return await checkAuth();\n  }\n});\n\n// Or just prompt the user:\nhistory.block('You have unsaved changes — leave anyway?');\n```\n\nFor pop navigations, a cancelled guard attempts to push the user back via `history.go(delta)`.\n\n### Query helpers\n\n```ts\nhistory.setQuery({ page: 2, q: 'hello' });   // patch params\nhistory.setQuery({ page: null });            // remove a key\nhistory.getQuery('page');                    // '2'\nhistory.searchParams;                        // URLSearchParams (read-only)\n```\n\nOr use the standalone helpers:\n\n```ts\nimport { parseQuery, stringifyQuery, mergeQuery } from '@buildwithdarsh/historyjs';\n```\n\n### Route matching\n\n```ts\nimport { matchPattern, buildPath } from '@buildwithdarsh/historyjs';\n\nhistory.matches<{ id: string }>('/users/:id');\n// → { pattern: '/users/:id', path: '/users/42', params: { id: '42' } }\n\nbuildPath('/users/:id', { id: 42 });\n// → '/users/42'\n```\n\nSupported syntax:\n- `:name` — named param, matches a single segment\n- `:name?` — optional segment\n- `:name*` — splat, matches the rest including slashes\n\n### Link interception\n\nOne call hijacks every same-origin `<a>` click. Modifier-clicks, off-origin links, download links, and links with `target` pass through unchanged.\n\n```ts\nimport { interceptLinks } from '@buildwithdarsh/historyjs';\n\nconst off = interceptLinks(history);\n// Optional config:\ninterceptLinks(history, {\n  selector: 'a[data-spa]',\n  root: document.getElementById('app'),\n  sameOriginOnly: true,\n});\n```\n\n### Virtual stack\n\n`history.entries` is an in-memory snapshot of every entry the manager has visited — great for breadcrumbs, history menus, or analytics.\n\n```ts\nhistory.entries.forEach((entry) => {\n  console.log(entry.index, entry.location.href, entry.timestamp);\n});\n```\n\n### Metrics\n\n```ts\nhistory.metrics; // { pushes, replaces, pops, cancelled }\n```\n\n### Scroll restoration\n\nEnabled by default on pop navigations. To opt out:\n\n```ts\nconst history = getHistory({ restoreScrollOnPop: false });\n```\n\nThe library sets `history.scrollRestoration = 'manual'` by default — pass `scrollRestoration: 'auto'` or `false` to opt out.\n\n### Singleton vs. instance\n\n`getHistory()` is a lazy singleton — convenient for app code. For tests or iframes, instantiate directly:\n\n```ts\nimport { HistoryManager } from '@buildwithdarsh/historyjs';\nconst history = new HistoryManager({ window: iframe.contentWindow! });\n```\n\n## React example\n\n```tsx\nimport { useEffect, useState, useSyncExternalStore } from 'react';\nimport { getHistory } from '@buildwithdarsh/historyjs';\n\nconst history = getHistory<{ page: string }>();\n\nexport function useHistoryEntry() {\n  return useSyncExternalStore(\n    (cb) => history.subscribe(cb),\n    () => history.entry,\n  );\n}\n```\n\n## Demo\n\nA full live playground (the page in `example/index.html`) is deployed at the project URL — push, replace, back/forward, query patching, route matching, guards, link interception, virtual stack, and a real-time event log are all wired up.\n\nTo run locally:\n\n```bash\nnpm install\nnpm run build\nnpx serve example\n```\n\n## Development\n\n```bash\nnpm install\nnpm run typecheck   # tsc --noEmit\nnpm test            # vitest run\nnpm run build       # rollup → dist/\nnpm run dev         # rollup --watch\n```\n\n## Publishing\n\n```bash\nnpm publish --access public\n```\n\nThe `prepublishOnly` script runs typecheck, tests, and the rollup build.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-7ae5ec357d53332ccbefbbcd650668c8"}