{"id":49516236,"url":"https://github.com/i-am-abdulazeez/stunk","last_synced_at":"2026-05-01T22:01:50.336Z","repository":{"id":277679628,"uuid":"922920607","full_name":"I-am-abdulazeez/stunk","owner":"I-am-abdulazeez","description":"A framework-agnostic state management library that keeps your app’s state clean and simple. It uses a fine-grained state model, breaking state into independent, manageable chunks.","archived":false,"fork":false,"pushed_at":"2026-04-29T06:58:11.000Z","size":537,"stargazers_count":168,"open_issues_count":1,"forks_count":13,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-29T08:35:13.893Z","etag":null,"topics":["angular","client-state-management","queflow","react","solid","state-management","svelte","typescript","vue","zustand-alternative"],"latest_commit_sha":null,"homepage":"https://stunk.dev/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/I-am-abdulazeez.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-27T10:29:15.000Z","updated_at":"2026-04-29T06:57:40.000Z","dependencies_parsed_at":null,"dependency_job_id":"9d4a795d-7b5f-412d-91f8-c11542042e62","html_url":"https://github.com/I-am-abdulazeez/stunk","commit_stats":null,"previous_names":["i-am-abdulazeez/stunk"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/I-am-abdulazeez/stunk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/I-am-abdulazeez%2Fstunk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/I-am-abdulazeez%2Fstunk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/I-am-abdulazeez%2Fstunk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/I-am-abdulazeez%2Fstunk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/I-am-abdulazeez","download_url":"https://codeload.github.com/I-am-abdulazeez/stunk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/I-am-abdulazeez%2Fstunk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32514340,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","client-state-management","queflow","react","solid","state-management","svelte","typescript","vue","zustand-alternative"],"created_at":"2026-05-01T22:01:49.511Z","updated_at":"2026-05-01T22:01:50.323Z","avatar_url":"https://github.com/I-am-abdulazeez.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Stunk\n\nStunk is a lightweight, framework-agnostic state management library built on atomic state principles. Break state into independent **chunks** — each one reactive, composable, and self-contained.\n\n- **Pronunciation**: _Stunk_ (a playful blend of \"state\" and \"chunk\")\n\n## Features\n\n- 🚀 **Lightweight** — 3.32kB gzipped, zero dependencies\n- ⚛️ **Atomic** — break state into independent chunks\n- 🔄 **Reactive** — fine-grained updates, only affected components re-render\n- 🧮 **Auto-tracking computed** — no dependency arrays, just call `.get()`\n- 🌐 **Async \u0026 Query layer** — loading, error, caching, deduplication, pagination built in\n- 🔁 **Mutations** — reactive POST/PUT/DELETE with automatic cache invalidation\n- 📦 **Batch updates** — group multiple updates into one render\n- 🔌 **Middleware** — logging, persistence, validation — plug anything in\n- ⏱️ **Time travel** — undo/redo state changes\n- 🔍 **TypeScript first** — full type inference, no annotations needed\n\n## Installation\n\n```bash\nnpm install stunk\n# or\nyarn add stunk\n# or\npnpm add stunk\n```\n\n📖 [Read the docs](https://stunk.dev)\n\n---\n\n## Core State\n\n```ts\nimport { chunk } from \"stunk\";\n\nconst count = chunk(0);\n\ncount.get();                    // 0\ncount.set(10);                  // set directly\ncount.set((prev) =\u003e prev + 1);  // updater function\ncount.peek();                   // read without tracking dependencies\ncount.reset();                  // back to 0\ncount.destroy();                // clear all subscribers\n```\n\n---\n\n## Computed — auto dependency tracking\n\nNo dependency arrays. Any chunk whose `.get()` is called inside the function is tracked automatically:\n\n```ts\nimport { chunk, computed } from \"stunk\";\n\nconst price    = chunk(100);\nconst quantity = chunk(3);\n\nconst total = computed(() =\u003e price.get() * quantity.get());\n\ntotal.get(); // 300\n\nprice.set(200);\ntotal.get(); // 600 — recomputed automatically\n```\n\nUse `.peek()` to read without tracking:\n\n```ts\nconst taxRate = chunk(0.1);\nconst subtotal = computed(() =\u003e price.get() * (1 + taxRate.peek()));\n// only recomputes when price changes\n```\n\n---\n\n## Async \u0026 Query\n\n```ts\nimport { asyncChunk } from \"stunk/query\";\n\nconst userChunk = asyncChunk(\n  async ({ id }: { id: number }) =\u003e fetchUser(id),\n  {\n    key: \"user\",              // deduplicates concurrent requests\n    keepPreviousData: true,   // no UI flicker on param changes\n    staleTime: 30_000,\n    onError: (err) =\u003e toast.error(err.message),\n  }\n);\n\nuserChunk.setParams({ id: 1 });\n// { loading: true, data: null, error: null }\n// { loading: false, data: { id: 1, name: \"...\" }, error: null }\n```\n\n---\n\n## Mutations\n\nReactive POST/PUT/DELETE — one function, always safe to await or fire and forget:\n\n```ts\nimport { mutation } from \"stunk/query\";\n\nconst createPost = mutation(\n  async (data: NewPost) =\u003e fetchAPI(\"/posts\", { method: \"POST\", body: data }),\n  {\n    invalidates: [postsChunk],                  // auto-reloads on success\n    onSuccess: () =\u003e toast.success(\"Created!\"),\n    onError:   (err) =\u003e toast.error(err.message),\n  }\n);\n\n// Fire and forget — safe\ncreatePost.mutate({ title: \"Hello\" });\n\n// Await for local control — no try/catch needed\nconst { data, error } = await createPost.mutate({ title: \"Hello\" });\nif (!error) router.push(\"/posts\");\n```\n\n---\n\n## Global Query Config\n\nSet defaults once for all async chunks and mutations:\n\n```ts\nimport { configureQuery } from \"stunk/query\";\n\nconfigureQuery({\n  query: {\n    staleTime: 30_000,\n    retryCount: 3,\n    onError: (err) =\u003e toast.error(err.message),\n  },\n  mutation: {\n    onError: (err) =\u003e toast.error(err.message),\n  },\n});\n```\n\n---\n\n## Middleware\n\n```ts\nimport { chunk } from \"stunk\";\nimport { logger, nonNegativeValidator } from \"stunk/middleware\";\n\nconst score = chunk(0, {\n  middleware: [logger(), nonNegativeValidator]\n});\n\nscore.set(10); // logs: \"Setting value: 10\"\nscore.set(-1); // throws: \"Value must be non-negative!\"\n```\n\n---\n\n## History (undo/redo)\n\n```ts\nimport { chunk } from \"stunk\";\nimport { history } from \"stunk/middleware\";\n\nconst count   = chunk(0);\nconst tracked = history(count);\n\ntracked.set(1);\ntracked.set(2);\ntracked.undo();   // 1\ntracked.redo();   // 2\ntracked.reset();  // 0 — clears history too\n```\n\n---\n\n## Persist\n\n```ts\nimport { chunk } from \"stunk\";\nimport { persist } from \"stunk/middleware\";\n\nconst theme = chunk\u003c\"light\" | \"dark\"\u003e(\"light\");\nconst saved = persist(theme, { key: \"theme\" });\n\nsaved.set(\"dark\");       // saved to localStorage\nsaved.clearStorage();    // remove from localStorage\n```\n\n---\n\n## React\n\n```tsx\nimport { chunk, computed } from \"stunk\";\nimport { useChunk, useChunkValue, useAsyncChunk, useMutation } from \"stunk/react\";\n\nconst counter = chunk(0);\nconst double  = computed(() =\u003e counter.get() * 2);\n\nfunction Counter() {\n  const [count, setCount] = useChunk(counter);\n  const doubled           = useChunkValue(double);\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003eCount: {count} — Doubled: {doubled}\u003c/p\u003e\n      \u003cbutton onClick={() =\u003e setCount((n) =\u003e n + 1)}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n\n// Async\nfunction PostList() {\n  const { data, loading, error } = useAsyncChunk(postsChunk);\n  if (loading) return \u003cp\u003eLoading...\u003c/p\u003e;\n  return \u003cul\u003e{data?.map(p =\u003e \u003cli key={p.id}\u003e{p.title}\u003c/li\u003e)}\u003c/ul\u003e;\n}\n\n// Mutation\nfunction CreatePostForm() {\n  const { mutate, loading, error } = useMutation(createPost);\n\n  const handleSubmit = async (data: NewPost) =\u003e {\n    const { error } = await mutate(data);\n    if (!error) router.push(\"/posts\");\n  };\n}\n```\n\n---\n\n## Package exports\n\n| Import             | Contents                                                        |\n| ------------------ | --------------------------------------------------------------- |\n| `stunk`            | `chunk`, `computed`, `select`, `batch`, `isChunk`, and more     |\n| `stunk/react`      | `useChunk`, `useChunkValue`, `useAsyncChunk`, `useInfiniteAsyncChunk`, `useMutation` |\n| `stunk/query`      | `asyncChunk`, `infiniteAsyncChunk`, `combineAsyncChunks`, `mutation`, `configureQuery` |\n| `stunk/middleware` | `history`, `persist`, `logger`, `nonNegativeValidator`          |\n\n---\n\n## Contributing\n\nContributions are welcome — open a [pull request](https://github.com/I-am-abdulazeez/stunk/pulls) or [issue](https://github.com/I-am-abdulazeez/stunk/issues).\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fi-am-abdulazeez%2Fstunk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fi-am-abdulazeez%2Fstunk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fi-am-abdulazeez%2Fstunk/lists"}