An open API service indexing awesome lists of open source software.

https://github.com/i-am-abdulazeez/stunk

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.
https://github.com/i-am-abdulazeez/stunk

angular client-state-management queflow react solid state-management svelte typescript vue zustand-alternative

Last synced: about 1 month ago
JSON representation

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.

Awesome Lists containing this project

README

          

# Stunk

Stunk 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.

- **Pronunciation**: _Stunk_ (a playful blend of "state" and "chunk")

## Features

- 🚀 **Lightweight** — 3.32kB gzipped, zero dependencies
- ⚛️ **Atomic** — break state into independent chunks
- 🔄 **Reactive** — fine-grained updates, only affected components re-render
- 🧮 **Auto-tracking computed** — no dependency arrays, just call `.get()`
- 🌐 **Async & Query layer** — loading, error, caching, deduplication, pagination built in
- 🔁 **Mutations** — reactive POST/PUT/DELETE with automatic cache invalidation
- 📦 **Batch updates** — group multiple updates into one render
- 🔌 **Middleware** — logging, persistence, validation — plug anything in
- ⏱️ **Time travel** — undo/redo state changes
- 🔍 **TypeScript first** — full type inference, no annotations needed

## Installation

```bash
npm install stunk
# or
yarn add stunk
# or
pnpm add stunk
```

📖 [Read the docs](https://stunk.dev)

---

## Core State

```ts
import { chunk } from "stunk";

const count = chunk(0);

count.get(); // 0
count.set(10); // set directly
count.set((prev) => prev + 1); // updater function
count.peek(); // read without tracking dependencies
count.reset(); // back to 0
count.destroy(); // clear all subscribers
```

---

## Computed — auto dependency tracking

No dependency arrays. Any chunk whose `.get()` is called inside the function is tracked automatically:

```ts
import { chunk, computed } from "stunk";

const price = chunk(100);
const quantity = chunk(3);

const total = computed(() => price.get() * quantity.get());

total.get(); // 300

price.set(200);
total.get(); // 600 — recomputed automatically
```

Use `.peek()` to read without tracking:

```ts
const taxRate = chunk(0.1);
const subtotal = computed(() => price.get() * (1 + taxRate.peek()));
// only recomputes when price changes
```

---

## Async & Query

```ts
import { asyncChunk } from "stunk/query";

const userChunk = asyncChunk(
async ({ id }: { id: number }) => fetchUser(id),
{
key: "user", // deduplicates concurrent requests
keepPreviousData: true, // no UI flicker on param changes
staleTime: 30_000,
onError: (err) => toast.error(err.message),
}
);

userChunk.setParams({ id: 1 });
// { loading: true, data: null, error: null }
// { loading: false, data: { id: 1, name: "..." }, error: null }
```

---

## Mutations

Reactive POST/PUT/DELETE — one function, always safe to await or fire and forget:

```ts
import { mutation } from "stunk/query";

const createPost = mutation(
async (data: NewPost) => fetchAPI("/posts", { method: "POST", body: data }),
{
invalidates: [postsChunk], // auto-reloads on success
onSuccess: () => toast.success("Created!"),
onError: (err) => toast.error(err.message),
}
);

// Fire and forget — safe
createPost.mutate({ title: "Hello" });

// Await for local control — no try/catch needed
const { data, error } = await createPost.mutate({ title: "Hello" });
if (!error) router.push("/posts");
```

---

## Global Query Config

Set defaults once for all async chunks and mutations:

```ts
import { configureQuery } from "stunk/query";

configureQuery({
query: {
staleTime: 30_000,
retryCount: 3,
onError: (err) => toast.error(err.message),
},
mutation: {
onError: (err) => toast.error(err.message),
},
});
```

---

## Middleware

```ts
import { chunk } from "stunk";
import { logger, nonNegativeValidator } from "stunk/middleware";

const score = chunk(0, {
middleware: [logger(), nonNegativeValidator]
});

score.set(10); // logs: "Setting value: 10"
score.set(-1); // throws: "Value must be non-negative!"
```

---

## History (undo/redo)

```ts
import { chunk } from "stunk";
import { history } from "stunk/middleware";

const count = chunk(0);
const tracked = history(count);

tracked.set(1);
tracked.set(2);
tracked.undo(); // 1
tracked.redo(); // 2
tracked.reset(); // 0 — clears history too
```

---

## Persist

```ts
import { chunk } from "stunk";
import { persist } from "stunk/middleware";

const theme = chunk<"light" | "dark">("light");
const saved = persist(theme, { key: "theme" });

saved.set("dark"); // saved to localStorage
saved.clearStorage(); // remove from localStorage
```

---

## React

```tsx
import { chunk, computed } from "stunk";
import { useChunk, useChunkValue, useAsyncChunk, useMutation } from "stunk/react";

const counter = chunk(0);
const double = computed(() => counter.get() * 2);

function Counter() {
const [count, setCount] = useChunk(counter);
const doubled = useChunkValue(double);

return (


Count: {count} — Doubled: {doubled}


setCount((n) => n + 1)}>+

);
}

// Async
function PostList() {
const { data, loading, error } = useAsyncChunk(postsChunk);
if (loading) return

Loading...

;
return
    {data?.map(p =>
  • {p.title}
  • )}
;
}

// Mutation
function CreatePostForm() {
const { mutate, loading, error } = useMutation(createPost);

const handleSubmit = async (data: NewPost) => {
const { error } = await mutate(data);
if (!error) router.push("/posts");
};
}
```

---

## Package exports

| Import | Contents |
| ------------------ | --------------------------------------------------------------- |
| `stunk` | `chunk`, `computed`, `select`, `batch`, `isChunk`, and more |
| `stunk/react` | `useChunk`, `useChunkValue`, `useAsyncChunk`, `useInfiniteAsyncChunk`, `useMutation` |
| `stunk/query` | `asyncChunk`, `infiniteAsyncChunk`, `combineAsyncChunks`, `mutation`, `configureQuery` |
| `stunk/middleware` | `history`, `persist`, `logger`, `nonNegativeValidator` |

---

## Contributing

Contributions are welcome — open a [pull request](https://github.com/I-am-abdulazeez/stunk/pulls) or [issue](https://github.com/I-am-abdulazeez/stunk/issues).

## License

MIT