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

@statedelta-apex/router

v3.0.0

Published

ApexStore Router - generic adapter between untyped DSL/engine and typed states via plugin system

Readme

@statedelta-apex/router

Adaptador genérico entre o mundo untyped (JSON DSL, engine tick-based) e o mundo typed (states). Algoritmo puro de roteamento — não conhece nenhum state type concreto. Todo conhecimento especialista vem via plugins declarativos.

Filosofia

Engines tick-based operam com strings: "hp", "set", { value: 30 }. Os states operam com tipos: CounterState.set(30), MatrixState.getCell(0, 1). O Router faz a ponte — recebe operações untyped, resolve o plugin correto via manifest, chama o método do state.

// Engine manda string → Router resolve → State executa
router.dispatch("hp", "dec", { value: 30 });
// internamente: state.decrement(30) via MethodMap ["decrement", "value"]

Princípios

  • States não sabem que o Router existe — zero acoplamento inverso.
  • Store não depende do Router — o Router depende do Store.
  • Plugins são declarativos — manifest declara o que existe, MethodMap mapeia para métodos do state.
  • Se o state não faz, o plugin não faz — plugin é mapa de rotas, não extensão de funcionalidade.
  • Router absorve DispatchResult — plugins executam a mutação, router monta { success, changed, value }.
  • Nunca throw no runtime — reads retornam undefined, writes retornam { success: false }. O tick nunca quebra.
  • Zero parsing no runtime — tudo pré-parseado pelo compiler DSL. Map lookup + handler call.

Instalação

pnpm add @statedelta-apex/router

Requer @statedelta-apex/store como peer dependency.

Quick Start

import { createStore } from "@statedelta-apex/store";
import { createRouter } from "@statedelta-apex/router";
import { createCounter } from "@statedelta-apex/derived-states";
import { counterPlugin } from "@statedelta-apex/derived-states";

// 1. Criar Store e Router
const store = createStore();
const router = createRouter(store);

// 2. Registrar plugins
router.register(counterPlugin);

// 3. Criar e registrar states
const hp = createCounter("hp", { value: 100, min: 0, max: 100 });
store.register(hp);

// 4. Operar via Router
router.get("hp"); // 100 (canonical)
router.get("hp", "percent"); // 1.0 (derived)

router.dispatch("hp", "dec", { value: 30 });
// { success: true, changed: true, value: 70 }

router.get("hp", "value"); // 70
router.get("hp", "percent"); // 0.7

Como Funciona

Plugin = Manifest + MethodMap

Cada state type registra um plugin. O plugin declara o que existe (manifest) e qual método do state chamar (handlers via MethodMap):

// MethodMap: string mapeia property/method, array mapeia method + params
type MethodMap = string | [string, ...string[]];

// Exemplo: statemachinePlugin (100% declarativo)
handlers: {
  canonical: "current",                      // → state.current
  derived: { states: "states", is: ["is", "state"] },
  queries: { can: ["can", "state"], available: "available" },
  actions: {
    transition: ["transition", "to"],        // → state.transition(params.to)
    forceState: ["force", "to"],             // → state.force(params.to)
    reset: "reset",                          // → state.reset()
  },
}

Function handlers existem apenas para exceções que o MethodMap não cobre (path resolution, spread args, predicates, conditional logic).

Três Tiers de Leitura

| Tier | API | Custo | Descrição | | ---- | ---------------------------------- | ----- | -------------------------------- | | 1 | router.get(id, field) | O(1) | Campo direto do getState() | | 2 | router.get(id, field, ...args) | O(1) | Derivação computada pelo handler | | 3 | router.query(id, method, params) | O(n+) | Busca, filtro, enumeração |

Tier 3 (query) valida params contra o schema do manifest e absorve throws — mesma garantia "nunca throw" do dispatch.

// Tier 1 — campo direto (só Counter e StateMachine)
router.get("hp", "value"); // state.getState().value

// Tier 2 — derived com handler
router.get("hp", "percent"); // handler computa
router.get("phase", "is", "playing"); // handler recebe arg

// Tier 3 — query O(n+)
router.query("inv", "filter", { where: (i) => i.damage > 10 });
router.query("phase", "available");

Escrita

const result = router.dispatch("hp", "dec", { value: 30 });
// { success: true, changed: true, value: 70 }

// Params validados contra o schema do manifest
const bad = router.dispatch("hp", "set", { value: "text" });
// { success: false, changed: false, error: "expected number, got string" }

Null como valor explícito

ParamDef.nullable: true permite passar null como valor legítimo (não tratado como "ausente"):

// matrix com defaultValue: null — limpar célula
router.dispatch("board", "set", { row: 0, col: 0, value: null });
// { success: true, changed: true, value: [[null, ...], ...] }

// Sem nullable: true, null seria rejeitado
// { success: false, error: "param 'value' cannot be null" }

Plugins oficiais marcam nullable: true onde faz sentido: matrix (set/fill/etc.), record (set), list (push/unshift). Counter/flags/statemachine não — seus params são scalars estritos.

O dispatch sempre retorna DispatchResult. Nunca throw. O value é sempre o canonical do state após a action. O changed é determinado pelo router via ref comparison.

API

createRouter(store, options?): Router

const router = createRouter(store, {
  onError: (err) => console.warn(err), // opcional
});

router.register(plugin)

Registra um plugin de state type.

router.get(stateId, field?, ...args): unknown

Leitura Tier 1+2.

router.get("hp"); // canonical: 100
router.get("hp", "value"); // field: 100
router.get("hp", "percent"); // derived: 0.7
router.get("config", "volume"); // fallback: 80

router.query(stateId, method, params?): unknown

Leitura Tier 3.

router.query("inv", "filter", { where: (i) => i.damage > 10 });
router.query("board", "find", { value: "X" });
router.query("phase", "can", { state: "playing" });

router.dispatch(stateId, action, params?): DispatchResult

Escrita com validação de params. value é sempre o canonical do state após a action.

router.dispatch("hp", "dec", { value: 30 });
router.dispatch("board", "set", { row: 0, col: 0, value: "X" });
router.dispatch("phase", "transition", { to: "playing" });

router.validate(stateId, kind, name, params?): ValidationResult

Validação sem execução — checa existência e tipos de params.

router.validate("hp", "get", "percent"); // { valid: true }
router.validate("hp", "dispatch", "fly"); // { valid: false, error: "..." }

router.batch(commands): DispatchResult[]

N dispatches em sequência. Cada um independente.

router.batch([
  { state: "hp", action: "dec", params: { value: 30 } },
  { state: "board", action: "set", params: { row: 0, col: 0, value: "X" } },
]);

router.transaction(fn) / router.simulate(fn)

Delega pro Store. Transaction = commit no sucesso, rollback no throw. Simulate = sempre rollback.

router.transaction(() => {
  router.dispatch("hp", "dec", { value: 30 });
  router.dispatch("board", "set", { row: 0, col: 0, value: "X" });
  // throw → rollback de TODOS os states
});

const wouldDie = router.simulate(() => {
  router.dispatch("hp", "dec", { value: 999 });
  return router.get("hp", "value") === 0;
});
// wouldDie === true, hp intacto

Introspection

router.getManifest("counter"); // StateManifest | undefined
router.listTypes(); // ["counter", "record", ...]
router.hasType("counter"); // true

Tratamento de Erros

O Router opera em runtime de engine — o tick nunca quebra. Erros são dados.

| Método | Sucesso | Falha | Throw? | | ------------ | ----------------------------------- | --------------------------- | ------ | | get() | valor | undefined | nunca | | query() | valor | undefined + lastError | nunca | | dispatch() | { success: true, changed, value } | { success: false, error } | nunca | | validate() | { valid: true } | { valid: false, error } | nunca |

query() valida params igual dispatch() e absorve throws do handler — predicates que explodem viram undefined + lastError, não exceptions.

lastError — Distinguir undefined legítimo vs erro

const value = router.get("hp", "percent");
if (router.lastError) {
  // undefined por erro
  console.warn(router.lastError.detail);
} else {
  // undefined legítimo (valor real do state)
}

onError callback

createRouter(store, {
  onError: (err) => engineLog.warn(err),
});

RouterError

interface RouterError {
  type:
    | "state_not_found"
    | "unknown_accessor"
    | "unknown_query"
    | "unknown_action"
    | "invalid_params";
  stateId: string;
  detail: string;
}

Plugins Disponíveis

Cada state package exporta seu plugin. O consumer registra os que precisa.

| Plugin | Pacote | Type | Canonical | | -------------------- | ----------------------------------- | ---------------- | --------------- | | counterPlugin | @statedelta-apex/derived-states | "counter" | valor numérico | | flagsPlugin | @statedelta-apex/derived-states | "flags" | flags ativas | | statemachinePlugin | @statedelta-apex/derived-states | "statemachine" | estado atual | | recordPlugin | @statedelta-apex/record-state | "record" | objeto completo | | collectionPlugin | @statedelta-apex/collection-state | "collection" | array de items | | matrixPlugin | @statedelta-apex/matrix-state | "matrix" | grid T[][] | | listPlugin | @statedelta-apex/list-state | "list" | array de items |

import {
  counterPlugin,
  flagsPlugin,
  statemachinePlugin,
} from "@statedelta-apex/derived-states";
import { recordPlugin } from "@statedelta-apex/record-state";
import { collectionPlugin } from "@statedelta-apex/collection-state";
import { matrixPlugin } from "@statedelta-apex/matrix-state";
import { listPlugin } from "@statedelta-apex/list-state";

router.register(counterPlugin);
router.register(recordPlugin);
router.register(collectionPlugin);
router.register(matrixPlugin);
router.register(listPlugin);
router.register(flagsPlugin);
router.register(statemachinePlugin);

Tipos Exportados

import { createRouter } from "@statedelta-apex/router";

import type {
  Router,
  RouterOptions,
  RouterError,
  StateManifest,
  StatePlugin,
  StateHandlers,
  MethodMap,
  DerivedHandler,
  QueryHandler,
  ActionHandler,
  FallbackHandler,
  FieldEntry,
  DerivedEntry,
  QueryEntry,
  ActionEntry,
  ParamDef,
  ReturnDef,
  ScalarType,
  DispatchResult,
  ValidationResult,
  BatchCommand,
} from "@statedelta-apex/router";

Referência Completa

Para documentação detalhada de cada operação por state type (com todos os exemplos de get/query/dispatch), consulte o API.md.

Para decisões de arquitetura e design interno, consulte o ARCHITECTURE.md.