Β· English Β· Korean
- π Vue 3 Composition API - Modern reactive state management
- β‘οΈ Zustand-like DX - Familiar API with
set,get, and selectors - π§βπ» Full TypeScript Support - Complete type safety and inference
- π Lightweight - Minimal bundle size, zero dependencies (except Vue)
- π― Selector-based Subscriptions - Optimize rendering with partial updates
- π Pluggable Middleware - Logger, Persist, ErrorBoundary, and more
- π§© Composable - Chain or compose multiple middlewares
- π¦ Tree-shakeable - Import only what you need
- β Production Ready - Fully tested with 30+ test cases
pnpm add zenon
# or
npm install zenon
# or
yarn add zenon// stores/counter.ts
import { createStore } from "zenon";
export const useCounter = () =>
createStore((set, get) => ({
count: 0,
increment: () => set({ count: get().count + 1 }, "increment"),
decrement: () => set({ count: get().count - 1 }, "decrement"),
reset: () => set({ count: 0 }, "reset"),
}));<!-- Counter.vue -->
<template>
<div>
<h2>Count: {{ count }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="reset">Reset</button>
</div>
</template>
<script setup lang="ts">
import { useCounter } from "./stores/counter";
const store = useCounter();
const count = store.useSelector((s) => s.count); // Partial subscription
const { increment, decrement, reset } = store;
</script>import { createStore, compose } from "zenon";
import { withLogger, withPersist, withErrorBoundary } from "zenon/plugins";
export const useCounter = () =>
createStore(
compose(
withLogger({ store: "counter", timestamp: true }),
withPersist("zenon-counter", {
storage: window.localStorage,
version: 1,
}),
withErrorBoundary({
onError: (error, actionName) => {
console.error(`Error in ${actionName}:`, error);
},
})
)((set, get) => ({
count: 0,
increment: () => set({ count: get().count + 1 }, "increment"),
reset: () => set({ count: 0 }, "reset"),
}))
);Zenon provides three powerful middleware plugins that can be composed together:
Logs all state changes to the console with timestamps.
import { withLogger } from "zenon/plugins";
withLogger({
store: "counter", // Store name for logging
timestamp: true, // Include timestamps (default: false)
expanded: false, // Auto-expand logs (default: false)
});Persists store state to localStorage or sessionStorage.
import { withPersist } from "zenon/plugins";
withPersist("zenon-counter", {
storage: window.localStorage, // default: localStorage
version: 1, // Version for migration control
partialKeys: ["count"], // Persist only specific keys
serialize: JSON.stringify, // Custom serializer
deserialize: JSON.parse, // Custom deserializer
onError: (error) => console.error(error), // Error handler
merge: (persisted, current) => ({ ...current, ...persisted }), // Merge strategy
});Catches and handles errors in store actions.
import { withErrorBoundary } from "zenon/plugins";
withErrorBoundary({
onError: (error, actionName) => {
// Handle error (e.g., send to error tracking service)
console.error(`Error in ${actionName}:`, error);
},
preventRollback: false, // Prevent state rollback on error (default: false)
rethrow: false, // Rethrow error after handling (default: false)
});import { createStore, compose } from "zenon";
import { withLogger, withPersist, withErrorBoundary } from "zenon/plugins";
// Compose style (recommended)
export const useStore = () =>
createStore(
compose(
withLogger({ store: "app" }),
withPersist("app-state"),
withErrorBoundary({ onError: console.error })
)((set, get) => ({
// Your store implementation
}))
);
// Or chain style
export const useStore = () =>
createStore(
withLogger({ store: "app" })(
withPersist("app-state")(
withErrorBoundary({ onError: console.error })((set, get) => ({
// Your store implementation
}))
)
)
);Creates a new store with reactive state management.
function createStore<T>(
initializer: (set: SetFunction<T>, get: GetFunction<T>) => T
): StoreApi<T>;Returns: StoreApi<T> with the following methods:
useSelector<U>(selector: (state: T) => U): ComputedRef<U>- Subscribe to partial statesubscribe(listener: Listener<T>): () => void- Subscribe to all state changessetSilent(patch: Partial<T>)- Update state without triggering subscribersgetState(): T- Get current state snapshot- Plus all store actions defined in your initializer
Subscribe to a specific part of the store state. Only triggers re-renders when the selected value changes.
const store = useCounter();
const count = store.useSelector((s) => s.count); // Subscribe only to count
const double = store.useSelector((s) => s.count * 2); // Derived values work tooSubscribe to all state changes with a callback function.
const unsubscribe = store.subscribe((state, prevState) => {
console.log("State changed:", { state, prevState });
});
// Cleanup
unsubscribe();Update state without notifying subscribers. Useful for batch updates or internal state changes.
store.setSilent({ count: 10 }); // Updates state without triggering listenersZenon is written in TypeScript and provides full type safety out of the box.
import { createStore, StoreApi, SetFunction, GetFunction } from "zenon";
// Type your store
type CounterState = {
count: number;
increment: () => void;
decrement: () => void;
};
export const useCounter = (): StoreApi<CounterState> =>
createStore<CounterState>((set, get) => ({
count: 0,
increment: () => set({ count: get().count + 1 }, "increment"),
decrement: () => set({ count: get().count - 1 }, "decrement"),
}));Exported Types:
StoreApi<T>- Store instance typeSetFunction<T>- State setter function typeGetFunction<T>- State getter function typeListener<T>- Subscribe callback typeStoreMiddleware<T>- Middleware function type
Zenon is thoroughly tested with 30+ test cases covering all features:
- β Core store functionality (reactivity, subscriptions, selectors)
- β All middleware plugins (logger, persist, error boundary)
- β Error handling and edge cases
- β TypeScript type safety
Run tests:
pnpm testContributions are always welcome! Please feel free to open an issue or submit a pull request.
Made with β€οΈ by AGUMON π¦
