I Built a Tiny State Management Library in TypeScript atostate


State management is one of those topics every frontend developer eventually bumps into.

At first, everything is simple:

  • local state
  • props
  • maybe a few shared variables

Then the app grows…
and suddenly state is everywhere, duplicated, out of sync, and hard to reason about.

Big libraries solve this but often with boilerplate, mental overhead, or framework lock-in.

So I decided to build my own minimal solution.

Meet atostate 👋




What is atostate?

atostate is a tiny tool state management library for JavaScript and TypeScript.

Its goal is very simple:

Provide a predictable global store with subscriptions and strong typing without complexity.

No framework assumptions.
No magic.
Just state + rules.




Why I built it

I wanted a library that:

  • Is small and readable
  • Works in vanilla JS or TS
  • Doesn’t force Redux-style architecture
  • Is easy to debug and reason about



Creating a store

import  createStore  from 'atostate';

type State = 
  count: number;
;

const store = createStore<State>(
  count: 0,
);
Enter fullscreen mode

Exit fullscreen mode

That’s it.
You now have a global store.




Updating state

State updates are explicit and predictable:

store.setState( count: 1 );

store.setState((prev) => (
  ...prev,
  count: prev.count + 1,
));
Enter fullscreen mode

Exit fullscreen mode

No direct mutation.
No hidden side effects.




Subscribing to changes

Subscribe to all state changes:

store.subscribe(() => 
  console.log('State changed:', store.getState());
);
Enter fullscreen mode

Exit fullscreen mode

Or subscribe to only what you care about:

store.subscribe(
  (state) => state.count,
  (count, prev) => 
    console.log('Count changed:', prev, '', count);
  
);
Enter fullscreen mode

Exit fullscreen mode

This makes updates efficient and intentional.




Selectors + equality checks

Subscriptions only re-run when their selected slice actually changes.

import  shallowEqual  from 'atostate';

store.subscribe(
  (state) => state.user,
  (user) => 
    console.log('User changed:', user);
  ,
  shallowEqual
);
Enter fullscreen mode

Exit fullscreen mode

No unnecessary re-runs.
No wasted work.




Actions: organizing logic cleanly

To keep logic out of UI code, atostate provides a lightweight actions API.

const counter = store.actions(( setState ) => (
  increment() 
    setState((s) => ( ...s, count: s.count + 1 ));
  ,
  reset() 
    setState( count: 0 );
  ,
));

counter.increment();
counter.reset();
Enter fullscreen mode

Exit fullscreen mode

Simple, explicit, and easy to test.




Optional Redux-style reducers

If you like reducers and dispatch, atostate supports that too optionally.

type Action =
  |  type: 'inc' 
  |  type: 'set'; value: number ;

function reducer(state: State, action: Action): State 
  switch (action.type) 
    case 'inc':
      return  ...state, count: state.count + 1 ;
    case 'set':
      return  ...state, count: action.value ;
    default:
      return state;
  


const store = createStore<State, Action>(
   count: 0 ,
   reducer 
);

store.dispatch( type: 'inc' );
Enter fullscreen mode

Exit fullscreen mode

Use it only if you want it.




Middleware support

Cross-cutting concerns are handled via middleware.



Logger

import  loggerMiddleware  from 'atostate';

createStore(
   count: 0 ,
   middleware: [loggerMiddleware('app')] 
);
Enter fullscreen mode

Exit fullscreen mode



Persistence

import  persistMiddleware  from 'atostate';

createStore(
   count: 0 ,
   middleware: [persistMiddleware('app-state')] 
);
Enter fullscreen mode

Exit fullscreen mode




TypeScript-first

atostate is written entirely in TypeScript:

  • Typed state
  • Typed actions
  • Typed selectors
  • Great autocomplete

No any. No guessing.




What atostate is not

atostate intentionally avoids:

  • Framework-specific APIs
  • Heavy abstractions
  • Hidden reactivity
  • Large surface area

It’s a core state engine, not a full framework.




When should you use it?

atostate is a good fit if you want:

  • A small global store
  • Shared state across components
  • Strong typing
  • Minimal learning curve
  • Full control over architecture

If you enjoy understanding how your tools work and don’t want unnecessary complexity you might enjoy using it.

Feedback, ideas, and PRs are very welcome 🙌



Source link