Skip to main content

Concepts

Amos is built around a small set of concepts: store, box, mutation, selector, action, and signal.

Store​

The store owns application state.

const store = createStore();

It has four public methods:

store.select(countBox);
store.dispatch(countBox.add(1));
store.snapshot();
store.subscribe(() => {});

Most applications use the store through React hooks, but the store itself has no React dependency.

Box​

A box is a named state node.

const countBox = numberBox('count');

The key must be unique. The box decides:

  • how the state is initialized
  • which mutations can update it
  • which selectors can read derived values
  • whether and how it can be persisted

Boxes are decentralized. You do not define one central reducer or one central state schema.

Mutation​

A mutation is a dispatchable object created by a box method.

dispatch(countBox.add(1));
dispatch(countBox.setState(10));

Mutations are synchronous. They receive the current state for one box and return the next state for that box.

Selector​

A selector derives a value from boxes or other selectors.

const selectDouble = selector((select) => {
return select(countBox) * 2;
});

select(selectDouble());

Selectors are explicit objects. They can carry debug labels, equality functions, cache options, and persistence row-loading metadata.

Action​

An action is a dispatchable function with access to dispatch and select.

const incrementLater = action(async (dispatch, select, amount: number) => {
await delay(100);
dispatch(countBox.add(amount));
});

Actions are the right place for async work, coordination across boxes, and workflows that dispatch several mutations.

Signal​

A signal is a dispatchable event.

const signOutSignal = signal<{ userId: number }>('user.signOut');

signOutSignal.subscribe((dispatch, select, data) => {
dispatch(currentUserIdBox.setState(0));
});

dispatch(signOutSignal({ userId: 1 }));

Signals decouple modules. One feature can publish that something happened, and other boxes or modules can react without importing the original action.

Shapes​

Amos ships immutable data structures such as List, Map, Record, RecordMap, ListMap, and MapMap.

Boxes use these shapes to expose state-specific methods.

const todosBox = recordMapBox('todos', TodoRecord, 'id');

dispatch(todosBox.mergeItem({ id: 1, title: 'Read docs' }));
select(todosBox.getItem(1));

This is the main Amos design difference: instead of writing one-off reducers for each state node, you pick a box whose operations match the state structure.