npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

apex-store

v0.3.0

Published

ApexStore — Typed state management with specialized primitives, derived types, transactions and registry pattern

Readme

apex-store

Typed state management com primitivos especializados, tipos derivados, transações aninhadas e registry pattern.

Um install, um import — zero peer dependency headaches.

import {
  createStore,
  createRecord,
  createCollection,
  createList,
  createMatrix,
  createCounter,
  createFlags,
  createStateMachine,
} from "apex-store";

Instalação

pnpm add apex-store

Por que apex-store?

State managers tradicionais (Redux, Zustand) armazenam objetos genéricos — o consumer escreve reducers pra mutar e selectors pra ler. O apex-store inverte isso: cada state é um store completo e autossuficiente que já sabe como armazenar, mutar, transacionar, notificar listeners e serializar.

O consumer só chama actions tipadas e getters tipados. Toda mecânica interna (shallow clone, strategy swap, middleware chain, transaction stack) é invisível.

Princípios

  • Quem não usa, não paga — features inativas têm custo zero. Sem subscriber? Zero overhead. Sem middleware? Zero overhead. Sem transaction? Zero overhead.
  • States são autossuficientes — funcionam standalone ou gerenciados por um Store. Transaction, subscribe, middleware, snapshot — tudo built-in.
  • Store orquestra, não implementa — delega tudo pros states via interface State<T>. Coordena transações cross-state, persistence global e lifecycle hooks.
  • Composição sobre herança — tipos derivados (Counter, Flags, StateMachine) compõem com um primitivo, expondo apenas actions de domínio. Mecânica interna 100% encapsulada.
  • Imutabilidade por convenção — toda mutação gera nova referência via shallow clone. Referências anteriores permanecem intactas.
  • Zero branches no hot path — o pipeline de mutação usa strategy swap (8 funções pré-definidas). A cada mutação é uma chamada direta — sem if/else.

Arquitetura

┌──────────────────────────────────────────────────────┐
│  Store (orquestrador)                                │
│  registry · transactions · persistence · lifecycle   │
│                                                      │
│  ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │
│  │  Record  │ │  Collection  │ │      Matrix      │ │
│  │  key/val │ │  ID-based    │ │    2D grid       │ │
│  └──────────┘ └──────────────┘ └──────────────────┘ │
│  ┌──────────┐                                        │
│  │   List   │          4 Primitivos                  │
│  │ positional│                                       │
│  └──────────┘                                        │
│                                                      │
│  ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │
│  │ Counter  │ │    Flags     │ │  StateMachine    │ │
│  │ num+bounds│ │ string set  │ │  FSM + graph     │ │
│  └──────────┘ └──────────────┘ └──────────────────┘ │
│                    3 Derivados                       │
└──────────────────────────────────────────────────────┘

Quick Start

import {
  createStore,
  createCounter,
  createCollection,
  createStateMachine,
} from "apex-store";

// States — funcionam standalone, Store é opt-in
const hp = createCounter("hp", { value: 100, min: 0, max: 100 });
const inventory = createCollection<Item>("inventory", []);
const phase = createStateMachine("phase", {
  initial: "menu",
  states: ["menu", "playing", "paused", "gameover"],
  transitions: [
    { from: "menu", to: "playing" },
    { from: "playing", to: ["paused", "gameover"] },
    { from: "paused", to: ["playing", "menu"] },
  ],
});

// Store — orquestra N states
const store = createStore();
store.register(hp);
store.register(inventory);
store.register(phase);

// Transaction atômica cross-state
store.transaction(() => {
  phase.transition("playing");
  hp.decrement(30);
  inventory.add({ id: "sword", name: "Sword", damage: 10 });
  // throw em qualquer ponto → rollback de TODOS os states tocados
});

// Simulação — sempre rollback, retorna resultado
const wouldDie = store.simulate(() => {
  hp.decrement(999);
  return hp.isMin;
});
// wouldDie === true, hp.value ainda 100

// Reatividade global — qualquer state muta, listener dispara
const unsub = store.subscribe((id, state, prev) => {
  console.log(`${id} mudou`);
});

// Persistence — snapshot e restore de todos os states
const snap = store.snapshot();
// ... tempo passa ...
store.restore(snap);

Store

API completa

O Store é o orquestrador central. Coordena registry, transações entre states, persistence global, factory registry e lifecycle hooks.

Registry

const store = createStore();

store.register(hp);
store.register(inventory);
store.register(board);

store.get<CounterState>("hp"); // busca tipada por ID
store.getByType("record"); // O(1) via index
store.has("hp"); // true
store.size; // 3
store.all(); // todos os states

store.cleanup((s) => s.getState().value === 0); // remove por condição
store.clear(); // remove todos

Factory Registry

Cria states via Store sem conhecer implementações concretas — inversion of control.

store.defineType("counter", (id, data) => createCounter(id, data));
store.defineType("collection", (id, data) => createCollection(id, data));

// Criação via Store — auto-register
const hp = store.create<CounterState>("counter", "hp", {
  value: 100,
  min: 0,
  max: 100,
});
store.has("hp"); // true

Transactions Cross-State

Lazy snapshot — states não tocados não pagam nada. Nesting arbitrário com adoption strategy.

// Atômico — commit no sucesso, rollback no throw
store.transaction(() => {
  hp.decrement(50);
  mp.decrement(30);
  phase.transition("gameover");
  // throw em qualquer ponto → rollback de TODOS os states tocados
});

// Simulação — SEMPRE rollback
const result = store.simulate(() => {
  hp.decrement(999);
  return hp.isMin; // true
});
// hp.value ainda intacto

// Nesting — simulate dentro de transaction
store.transaction(() => {
  hp.decrement(20);

  const wouldDie = store.simulate(() => {
    hp.decrement(999);
    return hp.isMin;
  });
  // hp.value ainda com -20 do nível pai

  if (wouldDie) phase.transition("gameover");
});

Reatividade Global

// Disparado quando QUALQUER state registrado muta
const unsub = store.subscribe((id, state, prevState) => {
  console.log(`${id} mudou`);
});

// Estado combinado de todos os states (referências diretas, zero clone)
store.getState();
// { hp: { value: 70, ... }, mp: { value: 20, ... }, ... }

Lifecycle Hooks

store.onRegister((state) => console.log(`Registered: ${state.id}`));
store.onUnregister((state) => console.log(`Removing: ${state.id}`));

Persistence Global

const snap = store.snapshot();
localStorage.setItem("game", JSON.stringify(snap));

// Depois...
const saved = JSON.parse(localStorage.getItem("game")!);
store.restore(saved);

Record

API completa

Armazenamento key-value tipado com acesso deep path, transações aninhadas e imutabilidade por convenção. O primitivo mais versátil — cobre configurações, entidades, formulários, estados de UI.

const user = createRecord("user", {
  name: "Anderson",
  age: 30,
  role: "admin",
  settings: { theme: "dark", lang: "pt-BR" },
});

Actions

// Set — type-safe (key é keyof T, value é T[K])
user.set("name", "Rosa");
user.set("age", 31);

// Merge — batch, uma mutação, um notify
user.merge({ role: "superadmin", age: 32 });

// Update — functional, atômico
user.update("age", (v) => v + 1);

// Deep path — sem dot-notation parsing
user.setPath(["settings", "theme"], "light");
user.updatePath(["settings", "lang"], (v) => (v as string).toUpperCase());

// Delete
user.delete("role");

// Replace / Reset
user.replace({ name: "New", age: 0, settings: { theme: "dark", lang: "en" } });
user.reset(); // volta ao estado da criação

Getters

user.get("name"); // "Anderson" — type-safe
user.getPath(["settings", "theme"]); // "dark"
user.getOr("nickname", "N/A"); // fallback se undefined

user.pick("name", "age"); // { name: "Anderson", age: 30 }
user.omit("settings"); // { name: "Anderson", age: 30, role: "admin" }

user.has("name"); // true
user.hasPath(["settings", "x"]); // false
user.keys(); // ["name", "age", "role", "settings"]
user.values(); // ["Anderson", 30, "admin", { ... }]
user.entries(); // [["name", "Anderson"], ...]

user.size; // 4
user.isEmpty; // false

Transactions

// Commit no sucesso, rollback no throw
user.transaction(() => {
  user.set("name", "temp");
  user.set("age", 0);
  throw new Error("abort");
});
// name ainda "Anderson", age ainda 30

// Simulate — sempre rollback
const preview = user.simulate(() => {
  user.set("age", 99);
  return user.get("age");
});
// preview === 99, user.get("age") ainda 30

Subscribe & Middleware

// Reatividade
const unsub = user.subscribe((state, prev) => {
  console.log(`name: ${prev.name} → ${state.name}`);
});

// Middleware — validação, logging, blocking
user.use((current, next, proceed) => {
  if (next.age < 0) return; // bloqueia mutações inválidas
  proceed();
});

Collection

API completa

Lista ordenada de items tipados com identidade por ID único. Toda operação CRUD é ID-based. Duplicatas são proibidas. Cobre users, products, inventory, decks de cartas — qualquer lista de entidades.

const inventory = createCollection<Item>("inventory", [
  { id: "sword", name: "Sword", damage: 10 },
  { id: "shield", name: "Shield", defense: 5 },
]);

Actions

// Add — valida unicidade por ID
inventory.add({ id: "potion", name: "Potion", heal: 20 });

// Batch add — N items, uma mutação, um notify
inventory.addMany([
  { id: "bow", name: "Bow", damage: 8 },
  { id: "arrow", name: "Arrow", damage: 1 },
]);

// Upsert — add se não existe, merge se existe
inventory.upsert({ id: "sword", name: "Sword +1", damage: 15 });

// Update — partial merge ou functional
inventory.update("sword", { damage: 20 });
inventory.update("sword", (item) => ({ ...item, damage: item.damage + 5 }));

// Update em batch por predicado
inventory.updateBy(
  (item) => item.damage > 10,
  (item) => ({ ...item, enchanted: true }),
);

// Remove — por ID ou predicado
inventory.remove("arrow");
inventory.removeBy((item) => item.damage < 5);

// Sort, replace, reset, clear
inventory.sort((a, b) => b.damage - a.damage);
inventory.replace(newItems);
inventory.reset(); // volta aos items da criação
inventory.clear(); // remove todos

Getters

inventory.getById("sword"); // { id: "sword", ... } | undefined
inventory.has("sword"); // true
inventory.at(0); // primeiro item
inventory.at(-1); // último item
inventory.indexOf("sword"); // 0

inventory.findBy((i) => i.damage > 10); // primeiro match
inventory.filterBy((i) => i.damage > 10); // todos os matches
inventory.every((i) => i.damage > 0); // true
inventory.some((i) => i.heal); // true
inventory.map((i) => i.name); // ["Sword", "Shield", ...]

inventory.size; // 5
inventory.isEmpty; // false
inventory.first; // primeiro item
inventory.last; // último item
inventory.items; // referência ao array (alias de getState())
inventory.idField; // "id"

Custom ID field

const cards = createCollection<Card>("cards", initialCards, {
  idField: "cardId", // usa "cardId" em vez de "id"
});

List

API completa

Lista ordenada tipada sem semântica de identidade. Qualquer tipo T, duplicatas válidas, acesso dual-end, capacidade opcional. A ordem é o que importa, não quem é o item. Base natural pra Queue (FIFO), Stack (LIFO), buffers, logs, histórico.

const tasks = createList<string>("tasks", ["process", "validate"]);
const scores = createList<number>("scores", [100, 85, 92]);
const buffer = createList<Message>("buffer", [], { maxSize: 100 });

Actions

// Dual-end — append/prepend
tasks.append("deploy");
tasks.prepend("urgent-fix");
tasks.appendMany(["test", "release"]);
tasks.prependMany(["lint", "format"]);

// Dual-end — remove e retorna
const next = tasks.removeFirst(); // "urgent-fix"
const last = tasks.removeLast(); // "release"

// Replace, reset, clear
tasks.replace(["a", "b", "c"]);
tasks.reset(); // volta ao estado da criação
tasks.clear(); // remove todos

Getters

tasks.peekFirst(); // olha o início sem remover
tasks.peekLast(); // olha o final sem remover
tasks.at(0); // acesso posicional
tasks.at(-1); // último
tasks.find((t) => t.startsWith("urgent"));
tasks.findIndex((t) => t === "deploy");
tasks.includes("process");
tasks.indexOf("process");
tasks.every((t) => t.length > 0);
tasks.some((t) => t.startsWith("urgent"));
tasks.toArray(); // cópia segura pra mutar

tasks.items; // referência direta
tasks.size; // count
tasks.isEmpty; // boolean
tasks.isFull; // true se size >= maxSize
tasks.maxSize; // Infinity ou o limite configurado

Capacidade

const buffer = createList<string>("buffer", [], { maxSize: 5 });

buffer.append("a");
buffer.append("b");
buffer.append("c");
buffer.append("d");
buffer.append("e");
buffer.isFull; // true

buffer.append("f"); // RangeError: would exceed maxSize

Validado em toda operação que aumenta o tamanho. Operações que reduzem nunca falham por capacidade.

Queue / Stack via composição

O ListState é o mecanismo — o consumer decide o padrão de acesso:

// FIFO — usa append + removeFirst
tasks.append("task-A");
tasks.append("task-B");
const next = tasks.removeFirst(); // "task-A"

// LIFO — usa append + removeLast
stack.append("frame-1");
stack.append("frame-2");
const top = stack.removeLast(); // "frame-2"

Matrix

API completa

Grid 2D tipado de dimensões fixas com structural sharing, neighbors, diagonals e ranges. Cobre tabuleiros de jogos, mapas, grids de layout, matrizes numéricas, puzzles, cellular automata.

const board = createMatrix<string | null>("board", 3, 3, null);
const grid = createMatrix<number>("heatmap", 100, 100, 0);

Dimensões e defaultValue são imutáveis — definidos na criação.

Actions

// Célula individual
board.setCell(0, 0, "X");
board.updateCell(1, 1, (v) => v ?? "O");

// Row inteira
grid.fillRow(0, 1); // primeira row = 1
grid.updateRow(2, (v, col) => v + col); // transform por coluna

// Coluna inteira
grid.fillCol(0, 5);

// Range retangular (from inclusivo, to exclusivo)
grid.fillRange(1, 3, 1, 3, 9);
// [0, 0, 0, 0]
// [0, 9, 9, 0]
// [0, 9, 9, 0]
// [0, 0, 0, 0]

// Fill total
board.fill(null); // limpa tudo

// Swap — troca duas células
board.setCell(0, 0, "X");
board.setCell(2, 2, "O");
board.swap(0, 0, 2, 2);
board.getCell(0, 0); // "O"
board.getCell(2, 2); // "X"

// Move — move célula, origem vira defaultValue
board.move(0, 0, 1, 1);
board.getCell(0, 0); // null

// Replace — substitui grid inteiro (valida dimensões)
board.replace([
  ["X", "O", "X"],
  ["O", "X", "O"],
  ["X", "O", "X"],
]);

// Reset — volta ao defaultValue
board.reset();

Getters

board.getCell(0, 0); // valor da célula
board.getRow(0); // cópia da row
board.getCol(0); // coluna como array
board.getRange(0, 2, 0, 2); // sub-grid T[][]

// Neighbors — cardinal (4) ou all (8)
board.getNeighbors(1, 1); // 4 vizinhos N/E/S/W
board.getNeighbors(1, 1, "all"); // 8 vizinhos (com diagonais)
board.getNeighbors(0, 0); // 2 vizinhos (canto)

// Diagonais
board.getDiagonal(); // principal
board.getDiagonal("anti"); // anti-diagonal

// Busca
board.find((v) => v === "X"); // primeira célula { row, col, value }
board.findAll((v) => v === "X"); // todas as células
board.count((v) => v !== null); // contagem

// Checks
board.isCellEmpty(0, 0); // true se === defaultValue
board.every((v) => v !== null); // todas preenchidas?
board.some((v) => v === "X"); // algum X?

board.rows; // número de linhas (imutável)
board.cols; // número de colunas (imutável)
board.defaultValue; // valor default (imutável)
board.cells; // T[][] (alias de getState())
board.size; // rows × cols

Structural Sharing

Mutações clonam apenas as rows afetadas. Se uma row não é tocada, a referência permanece a mesma:

const before = grid.getState();
grid.setCell(0, 0, 99);
const after = grid.getState();

before !== after; // true — grid mudou
before[0] !== after[0]; // true — row 0 mudou
before[1] === after[1]; // true — row 1 intacta (mesma referência)

Bounds Checking

board.setCell(5, 0, "X");
// RangeError: Position [5, 0] out of bounds for 3×3 matrix

board.fillRange(2, 1, 0, 3, "X");
// RangeError: Invalid range: from must be less than to

Counter

API completa

Valor numérico com bounds, step e clamping automático. Derived type sobre RecordState via DerivedState. Cobre HP/MP, ammo, cooldowns, XP, scores, progress bars, budgets, turn counters.

const hp = createCounter("hp", { value: 100, min: 0, max: 100 });
const xp = createCounter("xp", { value: 0, min: 0 }); // sem max
const cd = createCounter("cooldown", { value: 5, min: 0, max: 5 });

Actions

hp.increment(); // +step (default 1)
hp.increment(30); // +30
hp.decrement(); // -step
hp.decrement(50); // -50 (clampa a min)

hp.set(75); // direto (clampa aos bounds)
hp.fill(); // value = max
hp.empty(); // value = min
hp.reset(); // volta ao valor da criação

// Runtime bounds
hp.setMax(150); // level up
hp.setMin(10); // debuff
hp.setStep(5); // mudança de fire rate

Getters

hp.value; // 70
hp.initial; // 100
hp.min; // 0
hp.max; // 100
hp.step; // 1
hp.percentage; // 0.7 (0–1)
hp.isMin; // false
hp.isMax; // false
hp.canIncrement; // true
hp.canDecrement; // true

Cenários

// Game — HP com level up
const hp = createCounter("hp", { value: 100, min: 0, max: 100 });
hp.decrement(30); // take damage
hp.fill(); // potion
hp.setMax(150); // level up

// Ammo — shoot and reload
const ammo = createCounter("ammo", { value: 30, min: 0, max: 30 });
ammo.decrement(); // shoot
ammo.fill(); // reload

// Progress bar
const progress = createCounter("progress", { value: 0, min: 0, max: 10000 });
progress.increment(500);
progress.percentage; // 0.05

Flags

API completa

Set de flags (strings) com whitelist opcional, toggle e batch atômico. Derived type sobre RecordState. Cada flag é uma key — O(1) para has/add/remove. Cobre status effects, feature flags, permissions, pipeline stages, checkbox groups.

const status = createFlags("status", {
  initial: ["alive"],
  allowed: ["alive", "poisoned", "stunned", "shielded", "burning"],
});

const features = createFlags("features"); // sem whitelist — qualquer flag

Actions

status.add("poisoned"); // add (no-op se já existe)
status.remove("poisoned"); // remove (no-op se não existe)
status.toggle("shielded"); // inverte presença

// Batch atômico — uma mutação, um notify
status.addMany("strength", "haste", "shield");
status.removeMany("strength", "haste");

status.set(["alive", "shielded"]); // replace all
status.clear(); // remove tudo
status.reset(); // volta ao initial

Getters

status.has("poisoned"); // true — O(1)
status.hasAll("alive", "poisoned"); // true
status.hasAny("stunned", "frozen"); // false
status.hasNone("stunned", "frozen"); // true

status.all; // ["alive", "poisoned"] (novo array)
status.size; // 2
status.isEmpty; // false
status.allowed; // readonly ["alive", "poisoned", ...] | null

Cenários

// RPG — status effects com whitelist
const canAct =
  status.has("alive") && status.hasNone("stunned", "frozen", "sleeping");

// Feature flags — sem whitelist
const features = createFlags("features");
features.toggle("dark-mode");
features.toggle("beta-enabled");

// Permissions
const roles = createFlags("roles", {
  allowed: ["admin", "editor", "viewer"],
  initial: ["viewer"],
});
roles.add("editor");
roles.hasAny("admin", "editor"); // true

// ETL pipeline stages
const pipeline = createFlags("pipeline");
pipeline.add("extracted");
pipeline.add("validated");
pipeline.add("transformed");
pipeline.add("loaded");
pipeline.hasAll("extracted", "validated", "transformed", "loaded"); // true

StateMachine

API completa

Máquina de estados finita (FSM) com grafo de transições pre-compilado. Derived type sobre RecordState. O grafo é compilado uma vez no constructor em Map<string, Set<string>> — toda checagem de transição é O(1). Suporta wildcards, arrays e bypass.

const phase = createStateMachine("phase", {
  initial: "menu",
  states: ["menu", "playing", "paused", "gameover"],
  transitions: [
    { from: "menu", to: "playing" },
    { from: "playing", to: ["paused", "gameover"] },
    { from: "paused", to: ["playing", "menu"] },
  ],
});

TransitionRule

// Arrays — múltiplos destinos ou origens
{ from: "playing", to: ["paused", "gameover"] }
{ from: ["a", "b"], to: "c" }

// Wildcards — qualquer estado
{ from: "*", to: "error" }     // qualquer → error
{ from: "admin", to: "*" }     // admin → qualquer
{ from: "*", to: "*" }         // sem restrições

Actions

phase.transition("playing"); // validada pelo grafo
phase.transition("gameover"); // Error: transição não permitida

phase.force("gameover"); // bypass — ignora grafo (admin/debug)
phase.force("unknown"); // RangeError: estado não existe

phase.reset(); // volta ao initial

Getters

phase.current; // "playing"
phase.initial; // "menu"
phase.is("playing"); // true
phase.can("paused"); // true
phase.can("menu"); // false
phase.available; // ["paused", "gameover"]
phase.states; // readonly ["menu", "playing", "paused", "gameover"]

Cenários

// Order — cancel de qualquer ponto
const order = createStateMachine("order", {
  initial: "pending",
  states: ["pending", "paid", "shipped", "delivered", "cancelled"],
  transitions: [
    { from: "pending", to: "paid" },
    { from: "paid", to: "shipped" },
    { from: "shipped", to: "delivered" },
    { from: "*", to: "cancelled" }, // wildcard
  ],
});

// AI — behavior
const ai = createStateMachine("ai", {
  initial: "idle",
  states: ["idle", "patrol", "chase", "attack", "flee"],
  transitions: [
    { from: "idle", to: "patrol" },
    { from: "patrol", to: ["chase", "idle"] },
    { from: "chase", to: ["attack", "flee"] },
    { from: "attack", to: ["chase", "flee"] },
    { from: "flee", to: "idle" },
  ],
});

// Wizard — steps com back
const wizard = createStateMachine("wizard", {
  initial: "step1",
  states: ["step1", "step2", "step3", "complete"],
  transitions: [
    { from: "step1", to: "step2" },
    { from: "step2", to: ["step1", "step3"] },
    { from: "step3", to: ["step2", "complete"] },
  ],
});

// Connection — error recovery
const conn = createStateMachine("conn", {
  initial: "disconnected",
  states: ["disconnected", "connecting", "connected", "error"],
  transitions: [
    { from: "disconnected", to: "connecting" },
    { from: "connecting", to: ["connected", "error"] },
    { from: "connected", to: "disconnected" },
    { from: "error", to: ["connecting", "disconnected"] },
  ],
});

Shared Features

Todo state (primitivo ou derivado) implementa a interface State<T> e compartilha a mesma base:

Transaction

// Commit no sucesso, rollback no throw
state.transaction(() => {
  state.doSomething();
  state.doSomethingElse();
  // throw → rollback automático
});

// Simulate — SEMPRE rollback
const result = state.simulate(() => {
  state.doSomething();
  return state.getSomething();
});
// state intacto, result capturado

// Nesting arbitrário
state.transaction(() => {
  state.doA();

  const safe = state.simulate(() => {
    state.doB();
    return state.isValid();
  });

  if (!safe) throw new Error("invalid");
});

// API manual (se necessário)
state.beginTransaction();
state.commitTransaction();
state.rollback();
state.inTransaction; // boolean
state.transactionDepth; // number

Subscribe

const unsub = state.subscribe((current, prev) => {
  console.log("mudou:", prev, "→", current);
});

state.doSomething(); // listener disparado
unsub(); // para de ouvir

prevState vem de graça — a referência anterior já existe porque toda mutação é shallow clone. Zero custo extra.

Middleware

// Intercepta toda mutação
const remove = state.use((current, next, proceed) => {
  console.log("before:", current);
  proceed(); // permite mutação
  console.log("after:", state.getState());
});

// Validação — bloqueia não chamando proceed()
state.use((current, next, proceed) => {
  if (!isValid(next)) return; // mutação bloqueada, listeners não notificados
  proceed();
});

// Normalização — proceed(override) substitui valor antes de aplicar
state.use((_current, next, proceed) => {
  if (next.value > next.max) {
    proceed({ ...next, value: next.max }); // clamp
  } else {
    proceed();
  }
});

remove(); // remove middleware

Meta

Dados auxiliares persistidos junto ao state, mas separados do _state. Canal de reatividade próprio.

// Set / Get
state.meta("observedPaths", ["foo.bar", "hp.value"]);
const paths = state.meta<string[]>("observedPaths");

// Functional update
state.meta((cur) => ({ ...cur, tick: (cur.tick as number) + 1 }));

// Get all
state.meta(); // { observedPaths: [...], tick: 2 }

// Observe mudanças
const unsub = state.subscribeMeta((meta, prev) => {
  console.log("meta mudou:", meta);
});

Meta é coberto por transactions (rollback restaura meta) e persistence (snapshot inclui meta).

Snapshot / Restore

const snap = state.snapshot();
// { type: "record", id: "settings", data: { ... }, meta: { ... } }

localStorage.setItem("state", JSON.stringify(snap));

const saved = JSON.parse(localStorage.getItem("state")!);
state.restore(saved);

Standalone

Todo state funciona sem Store. O Store é opt-in — adiciona coordenação cross-state quando necessário.

// Funciona perfeitamente sozinho
const config = createRecord("config", { theme: "dark" });
config.set("theme", "light");
config.subscribe((s) => console.log(s));
config.transaction(() => {
  /* ... */
});
config.snapshot();

Encapsulamento dos Derivados

Tipos derivados (Counter, Flags, StateMachine) compõem com um primitivo internamente, mas não expõem os métodos do primitivo. Só actions de domínio são acessíveis:

const hp = createCounter("hp", { value: 100, min: 0, max: 100 });

hp.increment(); // ✓ domain action
hp.value; // ✓ domain getter
hp.subscribe(fn); // ✓ delegado via DerivedState
hp.transaction(fn); // ✓ delegado via DerivedState
store.register(hp); // ✓ Counter IS State

hp.set("value", 0); // ✗ não existe (RecordState.set)
hp.merge({}); // ✗ não existe
hp.delete("value"); // ✗ não existe

Todos os Exports

// Store (core)
import {
  createStateCore,
  createStore,
  DerivedState,
  assertSerializable,
} from "apex-store";

// Primitivos
import { createRecord, RecordState } from "apex-store";
import { createCollection, CollectionState } from "apex-store";
import { createList, ListState } from "apex-store";
import { createMatrix, MatrixState } from "apex-store";

// Derivados
import { createCounter, CounterState } from "apex-store";
import { createFlags, FlagsState } from "apex-store";
import { createStateMachine, StateMachineState } from "apex-store";

// Tipos
import type {
  State,
  StateCore,
  StateConfig,
  StateSnapshot,
  StateDefinition,
  StateFactory,
  IStore,
  StoreConfig,
  StoreSnapshot,
  StoreListener,
  StoreLifecycleHook,
  Listener,
  Middleware,
  RecordConfig,
  CollectionConfig,
  ListConfig,
  IListState,
  MatrixConfig,
  IMatrixState,
  CellPosition,
  CellPredicate,
  CounterData,
  CounterConfig,
  FlagsData,
  FlagsConfig,
  StateMachineData,
  StateMachineConfig,
  TransitionRule,
} from "apex-store";

Documentação Detalhada

| Doc | Descrição | | -------------------------------------- | ------------------------------------------------------------------------------------ | | Store | Core — createStateCore, createStore, DerivedState, assertSerializable, tipos | | Record | Primitivo key-value tipado com deep path | | Collection | Primitivo lista com identidade por ID | | List | Primitivo lista posicional com capacidade | | Matrix | Primitivo grid 2D com structural sharing | | Counter | Derivado numérico com bounds/step | | Flags | Derivado set de strings com whitelist | | StateMachine | Derivado FSM com grafo pre-compilado |

License

MIT