{"_id":"@antihero/statelite","_rev":"8-d2247994e7d1a0aee5a94007bf8242d4","name":"@antihero/statelite","dist-tags":{"latest":"2.1.0"},"versions":{"1.0.0":{"name":"@antihero/statelite","version":"1.0.0","keywords":["state","state-management","typescript","middleware","persistence"],"author":{"name":"antihero"},"license":"MIT","_id":"@antihero/statelite@1.0.0","maintainers":[{"name":"antihero","email":"lelianto.eko@gmail.com"}],"dist":{"shasum":"2ce121de5a974516e73e239b4528b5f37e58aa18","tarball":"https://registry.npmjs.org/@antihero/statelite/-/statelite-1.0.0.tgz","fileCount":13,"integrity":"sha512-ZmZLXam8tTVrETiqjmd9mLYzaYZI1IKArBQ/hYLqkN49komIrQ+9rPok7pcYLzHRtcU0wvBXg5/KNpYRTlnOnw==","signatures":[{"sig":"MEQCIDk4z30lNQOUuFkGzVuOLbzIUG1jwN3Ef/QFZUJ3BfAUAiAvZx7J5VVBu/a9IHW92rqg76Q35s7kSTqUwDAqPdRxfw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15152},"main":"index.js","gitHead":"a711800a9f4455ee63fd0393e653a0f72096c13a","scripts":{"test":"jest","build":"tsc"},"_npmUser":{"name":"antihero","email":"lelianto.eko@gmail.com"},"_npmVersion":"10.8.2","directories":{},"_nodeVersion":"18.20.6","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","typescript":"^5.7.3","@types/jest":"^29.5.14","@types/node":"^22.13.1","jest-environment-jsdom":"^29.7.0"},"_npmOperationalInternal":{"tmp":"tmp/statelite_1.0.0_1739362902485_0.7846214350993683","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@antihero/statelite","version":"1.0.1","keywords":["state","state-management","typescript","middleware","persistence","lightweight","store"],"author":{"name":"antihero"},"license":"MIT","_id":"@antihero/statelite@1.0.1","maintainers":[{"name":"antihero","email":"lelianto.eko@gmail.com"}],"homepage":"https://github.com/Lelianto/statelite#readme","bugs":{"url":"https://github.com/Lelianto/statelite/issues"},"dist":{"shasum":"14267babe2e54a7b9c5a93fe92e1483713e3bf78","tarball":"https://registry.npmjs.org/@antihero/statelite/-/statelite-1.0.1.tgz","fileCount":5,"integrity":"sha512-V86ng8T5iGXeKx301SX/3qWz90zFvmxEpm/B/1fOXvW4vTjdiYO1IgQjTB2aodFiUBIEpGl0bMbiQVtgtkEy5w==","signatures":[{"sig":"MEQCIHPM/McNj9HB5e6qdZsSRD/wpSlX+LOnd0lbYtsD8QUuAiBSD/f+u1Pk8qNwU315x29sLNfcPDSLhXJ9Y7bdT1ms4A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":8253},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"9a2835e294fc1b2cbcf6adad5cf0a1b3dae8fe40","scripts":{"test":"jest","build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"antihero","email":"lelianto.eko@gmail.com"},"repository":{"url":"git+https://github.com/Lelianto/statelite.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple TypeScript-based state management solution with middleware support and persistence functionality.","directories":{},"_nodeVersion":"18.20.6","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","typescript":"^5.7.3","@types/jest":"^29.5.14","@types/node":"^22.13.1","jest-environment-jsdom":"^29.7.0"},"_npmOperationalInternal":{"tmp":"tmp/statelite_1.0.1_1739364911871_0.4509005215157764","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@antihero/statelite","version":"1.0.2","keywords":["state","state-management","typescript","middleware","persistence","lightweight","store"],"author":{"name":"antihero"},"license":"MIT","_id":"@antihero/statelite@1.0.2","maintainers":[{"name":"antihero","email":"lelianto.eko@gmail.com"}],"homepage":"https://github.com/Lelianto/statelite#readme","bugs":{"url":"https://github.com/Lelianto/statelite/issues"},"dist":{"shasum":"ff09b28fc37cdb54d1a44249c8ac1015b23fc974","tarball":"https://registry.npmjs.org/@antihero/statelite/-/statelite-1.0.2.tgz","fileCount":7,"integrity":"sha512-xGHaB0PbHP2wBToR0JWjY1UyDe620kgYLcGua/x9k/N+D5CM7A194X04+5+o74Vil0bQKEp+GNWN4WY4R69pPQ==","signatures":[{"sig":"MEUCIQC8PS3AaS2/6KS/bnRaVHqycL7H4RLVikWD2tMexM0vPAIgXkxNhS9R2H2xtTsnHOOV7JvXftGQTw8AHrP7imVx1Q0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":4747},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"ce92ef854d9b0f8373c8c74b170168f12c34537c","scripts":{"test":"jest","build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"antihero","email":"lelianto.eko@gmail.com"},"repository":{"url":"git+https://github.com/Lelianto/statelite.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple TypeScript-based state management solution with middleware support and persistence functionality.","directories":{},"_nodeVersion":"18.20.6","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","typescript":"^5.7.3","@types/jest":"^29.5.14","@types/node":"^22.13.1","jest-environment-jsdom":"^29.7.0"},"_npmOperationalInternal":{"tmp":"tmp/statelite_1.0.2_1739365537636_0.4793190034119845","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@antihero/statelite","version":"1.0.3","keywords":["state","state-management","typescript","middleware","persistence","lightweight","store"],"author":{"name":"antihero"},"license":"MIT","_id":"@antihero/statelite@1.0.3","maintainers":[{"name":"antihero","email":"lelianto.eko@gmail.com"}],"homepage":"https://github.com/Lelianto/statelite#readme","bugs":{"url":"https://github.com/Lelianto/statelite/issues"},"dist":{"shasum":"a44df408c4091dad3458fd1da8b7fc257ab8180c","tarball":"https://registry.npmjs.org/@antihero/statelite/-/statelite-1.0.3.tgz","fileCount":3,"integrity":"sha512-gqGBpzYu/JA/xqO+IdA203emg3b62OdWlTkzC7n81kBz3110biOPYxfA4bTasKljGY+lVbhEDkRrKUq6wMiskg==","signatures":[{"sig":"MEYCIQCw9KJWdXUqFrQZsIDsIU+mp2IK+2b8nkrvDU2PGNJ/LwIhAN+csDMEr146oGwbvdJtrgr5Syn2UCprb46/LyeZVaYZ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":3527},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"7cad831e42943169150750397f455a418f09d1eb","scripts":{"test":"jest","build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"antihero","email":"lelianto.eko@gmail.com"},"repository":{"url":"git+https://github.com/Lelianto/statelite.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple TypeScript-based state management solution with middleware support and persistence functionality.","directories":{},"_nodeVersion":"18.20.6","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","typescript":"^5.7.3","@types/jest":"^29.5.14","@types/node":"^22.13.1","jest-environment-jsdom":"^29.7.0"},"_npmOperationalInternal":{"tmp":"tmp/statelite_1.0.3_1739366492038_0.2541653069565526","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@antihero/statelite","version":"1.0.4","keywords":["state","state-management","typescript","middleware","persistence","lightweight","store"],"author":{"name":"antihero"},"license":"MIT","_id":"@antihero/statelite@1.0.4","maintainers":[{"name":"antihero","email":"lelianto.eko@gmail.com"}],"homepage":"https://github.com/Lelianto/statelite#readme","bugs":{"url":"https://github.com/Lelianto/statelite/issues"},"dist":{"shasum":"4ece82dd241f40dce53feae2b763dae4fbcaa2e1","tarball":"https://registry.npmjs.org/@antihero/statelite/-/statelite-1.0.4.tgz","fileCount":3,"integrity":"sha512-7ZigYC70IOqzfthsLGlAkfAeHJkWKF5Ue72tM59dtiMiL8mEMAxtmMm5VVpfv3CRRQTQTMTTyBAmrtUIIynP9w==","signatures":[{"sig":"MEUCIE1p4lVNXRzpOmpO5Tomyy4XqOgQmdS/GRXq4SU0//rPAiEAr4UoTEzoxbWY1AixIR5bxdTyZs0ycQ16p+91wg0bnFk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":3811},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"21d88efba037c5c816041876050752486d877e7c","scripts":{"test":"jest","build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"antihero","email":"lelianto.eko@gmail.com"},"repository":{"url":"git+https://github.com/Lelianto/statelite.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple TypeScript-based state management solution with middleware support and persistence functionality.","directories":{},"_nodeVersion":"18.20.6","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","typescript":"^5.7.3","@types/jest":"^29.5.14","@types/node":"^22.13.1","jest-environment-jsdom":"^29.7.0"},"_npmOperationalInternal":{"tmp":"tmp/statelite_1.0.4_1739367637213_0.9843230151012996","host":"s3://npm-registry-packages-npm-production"}},"1.0.5":{"name":"@antihero/statelite","version":"1.0.5","keywords":["state","state-management","typescript","middleware","persistence","lightweight","store"],"author":{"name":"antihero"},"license":"MIT","_id":"@antihero/statelite@1.0.5","maintainers":[{"name":"antihero","email":"lelianto.eko@gmail.com"}],"homepage":"https://github.com/Lelianto/statelite#readme","bugs":{"url":"https://github.com/Lelianto/statelite/issues"},"dist":{"shasum":"60609ed13062cac9beae742b1be8480fefbb3900","tarball":"https://registry.npmjs.org/@antihero/statelite/-/statelite-1.0.5.tgz","fileCount":3,"integrity":"sha512-rLQJLDwu6+7AjEHCPbaKnITL5P1GM7S81oL/dcGEegunPpQRNj6YZau6wJxFYKeWsU7tohOpMPc4tTjc545/yg==","signatures":[{"sig":"MEQCIEkcXLE0GoKkI/hXeGCRcvqI7fhFiccoXFQCd3UP3CU5AiBd65P08NYD2YRN4Q2YvbmvRqzoAAqRdir7E1UzWSJ2JQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":4169},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"54b2b3e71e809421022bc2673fc8d489e1895937","scripts":{"test":"jest","build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"antihero","email":"lelianto.eko@gmail.com"},"repository":{"url":"git+https://github.com/Lelianto/statelite.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple TypeScript-based state management solution with middleware support and persistence functionality.","directories":{},"_nodeVersion":"18.20.6","dependencies":{"js-cookie":"^3.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","typescript":"^5.7.3","@types/jest":"^29.5.14","@types/node":"^22.13.1","@types/js-cookie":"^3.0.6","jest-environment-jsdom":"^29.7.0"},"_npmOperationalInternal":{"tmp":"tmp/statelite_1.0.5_1739370641790_0.7305067558371827","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@antihero/statelite","version":"2.0.0","keywords":["state","state-management","typescript","middleware","persistence","lightweight","store"],"author":{"name":"antihero"},"license":"MIT","_id":"@antihero/statelite@2.0.0","maintainers":[{"name":"antihero","email":"lelianto.eko@gmail.com"}],"homepage":"https://github.com/Lelianto/statelite#readme","bugs":{"url":"https://github.com/Lelianto/statelite/issues"},"dist":{"shasum":"8746ba3da62b6140b6638ba0c4cacd8a2ba8fe54","tarball":"https://registry.npmjs.org/@antihero/statelite/-/statelite-2.0.0.tgz","fileCount":11,"integrity":"sha512-LuSwW+4OeIKjGKnyQuW5c0P/abXYDSrwZAi58c90wXQC2mbp/xcwqyTunTsLxBpCKoPmMyI1g1oYgYq74tB8ug==","signatures":[{"sig":"MEQCIAkno/og9erBWySRgd4LSq04IZSvTE1g0qeWQYpRkmOJAiBXbsuYRWvBhtW4kaxR8E+qpLP6iANKRE9oL/yRLVbN9Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@antihero%2fstatelite@2.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":33587},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js","require":"./dist/index.js"}},"gitHead":"617e4f222cdbc1feb49e82acffb491befae3ab71","scripts":{"test":"jest --runInBand","build":"tsc","test:coverage":"jest --coverage --runInBand","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:0cc7c808-db43-461d-9774-0232980e7359"}},"repository":{"url":"git+https://github.com/Lelianto/statelite.git","type":"git"},"_npmVersion":"11.16.0","description":"A tiny framework-agnostic TypeScript state store with selectors and pluggable persistence.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.2.5","typescript":"^5.7.3","@types/jest":"^29.5.14","@types/node":"^22.13.1","@types/js-cookie":"^3.0.6","jest-environment-jsdom":"^29.7.0"},"_npmOperationalInternal":{"tmp":"tmp/statelite_2.0.0_1785198529787_0.13411888474234246","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@antihero/statelite","version":"2.1.0","description":"A tiny framework-agnostic TypeScript state store with selectors and pluggable persistence.","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","require":"./dist/index.js","default":"./dist/index.js"}},"sideEffects":false,"scripts":{"build":"tsc","test":"jest --runInBand","test:coverage":"jest --coverage --runInBand","prepublishOnly":"npm test && npm run build"},"keywords":["state","state-management","typescript","middleware","persistence","lightweight","store"],"author":{"name":"antihero"},"license":"MIT","devDependencies":{"@types/jest":"^29.5.14","@types/js-cookie":"^3.0.6","@types/node":"^22.13.1","jest":"^29.7.0","jest-environment-jsdom":"^29.7.0","ts-jest":"^29.2.5","typescript":"^5.7.3"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"repository":{"type":"git","url":"git+https://github.com/Lelianto/statelite.git"},"bugs":{"url":"https://github.com/Lelianto/statelite/issues"},"homepage":"https://github.com/Lelianto/statelite#readme","engines":{"node":">=18"},"gitHead":"307a08065da6b7dc4a45a4fe64b0650990bedac1","_id":"@antihero/statelite@2.1.0","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-SfyKanSD1x4Fz1iHyXhnLLh3asjM7XZleOC9QaI7rENtMt4boic9JlnhPIoPi23HLSwNfQXbdF6TTtHERJaiMA==","shasum":"28403e502c199212c29215995e6f0b31e40d9957","tarball":"https://registry.npmjs.org/@antihero/statelite/-/statelite-2.1.0.tgz","fileCount":19,"unpackedSize":54742,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@antihero%2fstatelite@2.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDz/7b414UW08dkU9xb8gLSYbE7zmgUBkegyoihWG9yTgIgLN0KO76kWQYtba75DaeTqUmUbRQsxdwfcV1SxKlv/cw="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:0cc7c808-db43-461d-9774-0232980e7359"}},"directories":{},"maintainers":[{"name":"antihero","email":"lelianto.eko@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/statelite_2.1.0_1785201884562_0.8898846082654011"},"_hasShrinkwrap":false}},"time":{"created":"2025-02-12T12:21:42.401Z","modified":"2026-07-28T01:24:45.061Z","1.0.0":"2025-02-12T12:21:42.724Z","1.0.1":"2025-02-12T12:55:12.035Z","1.0.2":"2025-02-12T13:05:37.814Z","1.0.3":"2025-02-12T13:21:32.246Z","1.0.4":"2025-02-12T13:40:37.375Z","1.0.5":"2025-02-12T14:30:41.960Z","2.0.0":"2026-07-28T00:28:49.957Z","2.1.0":"2026-07-28T01:24:44.728Z"},"bugs":{"url":"https://github.com/Lelianto/statelite/issues"},"author":{"name":"antihero"},"license":"MIT","homepage":"https://github.com/Lelianto/statelite#readme","keywords":["state","state-management","typescript","middleware","persistence","lightweight","store"],"repository":{"type":"git","url":"git+https://github.com/Lelianto/statelite.git"},"description":"A tiny framework-agnostic TypeScript state store with selectors and pluggable persistence.","maintainers":[{"name":"antihero","email":"lelianto.eko@gmail.com"}],"readme":"# Statelite\n\n[![CI](https://github.com/Lelianto/statelite/actions/workflows/main.yml/badge.svg)](https://github.com/Lelianto/statelite/actions/workflows/main.yml)\n[![npm version](https://img.shields.io/npm/v/@antihero/statelite.svg)](https://www.npmjs.com/package/@antihero/statelite)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@antihero/statelite)](https://bundlephobia.com/package/@antihero/statelite)\n[![license](https://img.shields.io/npm/l/@antihero/statelite.svg)](./LICENSE)\n\nA tiny, type-safe, framework-agnostic state store for TypeScript and JavaScript.\nStatelite provides reactive selectors and optional persistence without importing a\nUI framework or browser API into its core.\n\n```ts\nimport { createStatelite } from '@antihero/statelite';\n\nconst counter = createStatelite({ count: 0 });\n\ncounter.subscribe(\n  (state) => state.count,\n  (count) => console.log('Count:', count)\n);\n\ncounter.setState((state) => ({ count: state.count + 1 }));\n```\n\n## Why Statelite?\n\n- **Framework-agnostic** — use the same store with vanilla JavaScript, React, Vue,\n  Svelte, Angular, Solid, server-side rendering, workers, and tests.\n- **Tiny core** — no runtime dependencies.\n- **Type-safe** — state, updater, selector, and listener types are inferred.\n- **Reactive selectors** — subscribers only run when their selected value changes.\n- **Predictable updates** — shallow merge, explicit replacement, reset, action\n  labels, and previous-state snapshots.\n- **Pluggable persistence** — local storage, session storage, IndexedDB, React\n  Native storage, server storage, or an in-memory test adapter.\n- **SSR-safe** — the core does not access `window`, `document`, cookies, or storage.\n\n## Installation\n\n```bash\nnpm install @antihero/statelite\n```\n\nStatelite requires Node.js 18 or newer for development and server-side usage.\nThe browser build uses standard ES2018 APIs.\n\n## Quick start\n\n```ts\nimport { createStatelite } from '@antihero/statelite';\n\ninterface CounterState {\n  count: number;\n  owner: string;\n}\n\nconst counter = createStatelite<CounterState>({\n  count: 0,\n  owner: 'team',\n});\n\nconst unsubscribe = counter.subscribe((state, previousState, action) => {\n  console.log(action, previousState.count, '->', state.count);\n});\n\ncounter.setState(\n  (state) => ({ count: state.count + 1 }),\n  { action: 'counter/increment' }\n);\n\ncounter.getState(); // { count: 1, owner: 'team' }\ncounter.select((state) => state.count); // 1\n\ncounter.reset();\nunsubscribe();\n```\n\n`setState` shallow-merges object updates by default:\n\n```ts\ncounter.setState({ count: 10 });\n```\n\nReplace the entire state when required:\n\n```ts\ncounter.setState(\n  { count: 0, owner: 'new-owner' },\n  { replace: true, action: 'counter/replace' }\n);\n```\n\n## Reactive selectors\n\nA selector subscription is only notified when its selected value changes according\nto `Object.is`:\n\n```ts\nconst unsubscribe = counter.subscribe(\n  (state) => state.count,\n  (count, previousCount, action) => {\n    console.log({ count, previousCount, action });\n  }\n);\n```\n\nFor object or array slices, use the built-in `shallowEqual` helper or pass a\ncustom equality function:\n\n```ts\nimport { shallowEqual } from '@antihero/statelite';\n\nconst unsubscribe = store.subscribe(\n  (state) => ({ page: state.page, filter: state.filter }),\n  (selection) => renderRows(selection),\n  {\n    equalityFn: shallowEqual,\n    fireImmediately: true,\n  }\n);\n```\n\n`equality` remains supported as an alias for backward compatibility.\n\n## Middleware\n\nMiddleware can inspect, transform, or block a transaction before it is committed:\n\n```ts\nimport { createStatelite, type StateMiddleware } from '@antihero/statelite';\n\nconst logger: StateMiddleware<{ count: number }> = (transaction, next) => {\n  console.log(transaction.action, transaction.previousState, transaction.nextState);\n  next();\n};\n\nconst preventNegative: StateMiddleware<{ count: number }> = (\n  transaction,\n  next\n) => {\n  if (transaction.nextState.count >= 0) next(transaction);\n};\n\nconst counter = createStatelite(\n  { count: 0 },\n  { middleware: [logger, preventNegative] }\n);\n```\n\nMiddleware runs in declaration order. Calling `next()` continues the pipeline;\nnot calling it blocks the update.\n\n## Async actions\n\n`createAsyncAction` provides lifecycle hooks, an `AbortSignal`, and protection\nagainst stale requests. A newer call aborts the previous call by default:\n\n```ts\nimport { createAsyncAction } from '@antihero/statelite';\n\nconst loadUser = createAsyncAction(\n  userStore,\n  async ({ signal }, userId: string) => {\n    const response = await fetch(`/api/users/${userId}`, { signal });\n    return response.json() as Promise<User>;\n  },\n  {\n    onStart: (_args, store) => store.setState({ loading: true, error: null }),\n    onSuccess: (user, _args, store) => store.setState({ user }),\n    onError: (error, _args, store) => store.setState({ error }),\n    onFinally: (_args, store) => store.setState({ loading: false }),\n  }\n);\n\nawait loadUser('42');\nloadUser.abort();\n```\n\nSet `latest: false` to allow concurrent calls.\n\n## Redux DevTools\n\nConnect a store to the Redux DevTools browser extension:\n\n```ts\nimport { connectDevtools } from '@antihero/statelite';\n\nconst devtools = connectDevtools(counter, { name: 'Counter' });\n\n// Remove both subscriptions when the store is no longer needed.\ndevtools.disconnect();\n```\n\nThe bridge is SSR-safe, does nothing when the extension is unavailable, forwards\naction labels, and supports jump-to-state time travel.\n\n## Persistence\n\nPersistence is separate from the core and works with synchronous or asynchronous\nstorage:\n\n```ts\nimport {\n  createStatelite,\n  createWebStorage,\n  persistStore,\n} from '@antihero/statelite';\n\nconst preferences = createStatelite({\n  theme: 'light',\n  sidebarOpen: true,\n  sessionToken: '',\n});\n\nconst persistence = persistStore(preferences, {\n  key: 'app-preferences',\n  storage: createWebStorage('localStorage'),\n  version: 1,\n  ttl: 1000 * 60 * 60 * 24,\n  partialize: (state) => ({\n    theme: state.theme,\n    sidebarOpen: state.sidebarOpen,\n  }),\n  onError: (error) => {\n    console.error('Could not persist preferences', error);\n  },\n});\n\nawait persistence.rehydrate();\n```\n\nAvailable controller methods:\n\n```ts\nawait persistence.rehydrate();\nawait persistence.clear();\npersistence.hasHydrated();\npersistence.unsubscribe();\n```\n\n### Migrations\n\nIncrement `version` whenever the persisted shape changes:\n\n```ts\nconst persistence = persistStore(store, {\n  key: 'settings',\n  storage: createWebStorage(),\n  version: 2,\n  migrate: (persistedState, persistedVersion) => {\n    if (persistedVersion === 1) {\n      const oldState = persistedState as { darkMode: boolean };\n      return { theme: oldState.darkMode ? 'dark' : 'light' };\n    }\n\n    return persistedState as { theme: string };\n  },\n});\n```\n\nWhen `ttl` is set, expired state is removed during hydration and the store keeps\nits current initial state.\n\n### Custom or asynchronous storage\n\n```ts\nimport type { StorageAdapter } from '@antihero/statelite';\n\nconst storage: StorageAdapter = {\n  getItem: async (key) => database.read(key),\n  setItem: async (key, value) => database.write(key, value),\n  removeItem: async (key) => database.remove(key),\n};\n```\n\n## Framework integration\n\nStatelite implements an external-store contract: read a snapshot with `getState`\nand listen for changes with `subscribe`.\n\n### React\n\n```tsx\nimport { useSyncExternalStore } from 'react';\n\nfunction useCounter() {\n  return useSyncExternalStore(\n    counter.subscribe,\n    counter.getState,\n    counter.getInitialState\n  );\n}\n\nfunction Counter() {\n  const state = useCounter();\n\n  return (\n    <button onClick={() => counter.setState((s) => ({ count: s.count + 1 }))}>\n      Count: {state.count}\n    </button>\n  );\n}\n```\n\n### Vue\n\n```ts\nimport { onScopeDispose, shallowRef } from 'vue';\n\nexport function useStatelite<T extends object>(store: StateliteStore<T>) {\n  const state = shallowRef(store.getState());\n  const unsubscribe = store.subscribe((nextState) => {\n    state.value = nextState;\n  });\n\n  onScopeDispose(unsubscribe);\n  return state;\n}\n```\n\n### Svelte\n\n```ts\nconst svelteCounter = {\n  subscribe(run: (state: Readonly<CounterState>) => void) {\n    run(counter.getState());\n    return counter.subscribe((state) => run(state));\n  },\n};\n```\n\nAngular, Solid, Preact, Lit, and other reactive systems can use the same\n`getState`/`subscribe` bridge.\n\n## SSR\n\nCreate a fresh store for every server request. Do not share a mutable singleton\nbetween users:\n\n```ts\nexport const createRequestStore = (initialCount = 0) =>\n  createStatelite({ count: initialCount });\n```\n\n`createWebStorage` is lazy and safe to import on the server, but its methods must\nonly be called in a browser. Set `autoHydrate: false` when hydration timing is\ncontrolled by your framework.\n\n## API reference\n\n### `createStatelite(initialState, options?)`\n\nCreates a store and returns:\n\n| Method | Description |\n| --- | --- |\n| `getState()` | Returns the current readonly state snapshot. |\n| `getInitialState()` | Returns the initial readonly snapshot. |\n| `setState(update, options?)` | Updates or replaces state. |\n| `reset(action?)` | Restores the initial state. |\n| `subscribe(listener)` | Subscribes to every effective state change. |\n| `subscribe(selector, listener, options?)` | Subscribes to a selected slice. |\n| `select(selector)` | Reads a derived value without subscribing. |\n| `destroy()` | Removes listeners and prevents future updates. |\n\n`options.middleware` accepts an ordered array of `StateMiddleware` functions.\n\n### `setState` options\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `replace` | `boolean` | `false` | Replace instead of shallow-merging state. |\n| `action` | `string` | `undefined` | Label forwarded to subscribers and tooling. |\n\n### Selector subscription options\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `equalityFn` | `(a, b) => boolean` | `Object.is` | Determines whether a slice changed. |\n| `equality` | `(a, b) => boolean` | `Object.is` | Backward-compatible alias for `equalityFn`. |\n| `fireImmediately` | `boolean` | `false` | Runs the listener during subscription. |\n\n### `persistStore(store, options)`\n\n| Option | Required | Description |\n| --- | --- | --- |\n| `key` | Yes | Storage key. |\n| `storage` | Yes | Sync or async storage adapter. |\n| `version` | No | Persisted schema version; defaults to `0`. |\n| `partialize` | No | Selects which state fields are persisted. |\n| `migrate` | No | Converts older persisted versions. |\n| `merge` | No | Combines persisted and current state. |\n| `serialize` / `deserialize` | No | Custom payload encoding. |\n| `autoHydrate` | No | Automatically starts hydration; defaults to `true`. |\n| `ttl` | No | Number of milliseconds before persisted data expires. |\n| `onError` | No | Handles storage and serialization failures. |\n\n### Additional helpers\n\n| Helper | Description |\n| --- | --- |\n| `shallowEqual(a, b)` | Shallow equality helper for selector subscriptions. |\n| `createAsyncAction(store, handler, options?)` | Creates an abortable async action with lifecycle hooks. |\n| `connectDevtools(store, options?)` | Connects a store to Redux DevTools. |\n\n## Migration from 1.x\n\nVersion 2 removes the direct `js-cookie` dependency and the\n`createStatelite(initialState, { persistKey })` option.\n\n```ts\n// 1.x\ncreateStatelite(initialState, { persistKey: 'settings' });\n\n// 2.x\nconst store = createStatelite(initialState);\npersistStore(store, {\n  key: 'settings',\n  storage: createWebStorage('localStorage'),\n});\n```\n\nCookie persistence can still be implemented with a custom `StorageAdapter`.\n\n## Development\n\n```bash\nnpm ci\nnpm test\nnpm run build\nnpm pack --dry-run\n```\n\nArchitecture notes and the longer roadmap are available in\n[ANALYSIS.md](https://github.com/Lelianto/statelite/blob/main/ANALYSIS.md).\n\n## Publishing\n\nReleases are published to npm from GitHub Actions using npm Trusted Publishing:\n\n1. Configure `@antihero/statelite` on npm with a GitHub Actions trusted publisher:\n   owner `Lelianto`, repository `statelite`, workflow `publish.yml`.\n2. Update the version in `package.json` and `package-lock.json`.\n3. Merge the release commit into `main`.\n4. Create a GitHub Release with a matching tag, for example `v2.0.0`.\n5. The workflow validates the tag, runs tests and build, checks the tarball, then\n   publishes the public package.\n\nNo long-lived `NPM_TOKEN` is required.\n\n## License\n\n[MIT](./LICENSE)\n","readmeFilename":"README.md"}