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-axiom/realm-system

v0.1.0

Published

RealmSystem — composition root funcional. Cria engines, boota definitions, executa ticks via TickRunner

Readme

@statedelta-axiom/realm-system

Composition root do sistema statedelta-actions. Cria engines, boota definitions, executa ticks.

O RealmSystem (RS) é o System — o porteiro que virtualiza o Realm. Recebe physics (leis do mundo), cria toda a infraestrutura internamente, aceita definitions no boot, e executa ticks via TickRunner. O customer não toca em engines nem handlers — só em physics, definitions e a API pública.

Filosofia

RS = composition root. Não é engine, não é orquestrador. É quem monta tudo e opera. O customer diz o que quer (physics + definitions). O RS decide como fazer (engines, handlers, wiring).

Duas fases de construção (ADR-051). Physics define as leis do mundo (imutável). Boot define os recursos iniciais (states, actions, rules, events, views). São concerns separados — regras do jogo vs estado inicial do jogo.

States via Store (ADR-052). Todo state é criado via store.create() com factory registry. O RS nunca instancia states diretamente. O Store do apex-store coordena transações, snapshot/restore e lifecycle.

Handlers são built-in (ADR-044). O RS cria e registra 6 handlers internamente (dispatch, action, emit, halt, notify, request). O customer não passa handlers — são concern do RS que tem o contexto necessário pra montá-los.

O Realm não sabe onde está. Como o Neo na Matrix — opera com o que recebeu. Não sabe se está num RS, Sandbox, teste, ou é child de outra sandbox. O System processa e virtualiza.

Boot com erro não transiciona. Se o boot tiver erros, o RS emite error e permanece em "created". O realm não bootou.

Erro no step re-throw. Se o TickRunner lança exceção, o RS emite error e re-lança. O tick não completa — transition não capturada, step:complete não emitido.

Instalação

pnpm add @statedelta-axiom/realm-system

Quick Start

import { createRealmSystem } from "@statedelta-axiom/realm-system";

// 1. Construção — physics define as leis do mundo
const rs = createRealmSystem({
  enableViews: true,
  enableEvents: true,
  temporal: { stepSize: 4, cycleSize: 10 },
});

// 2. Boot — definitions populam o realm, env injeta birth data
const bootResult = rs.boot({
  states: [
    { id: "hp", type: "counter", data: { value: 100, min: 0, max: 100 } },
    { id: "score", type: "counter", data: { value: 0 } },
    { id: "status", type: "flags", data: { initial: ["alive"] } },
  ],
  actions: [
    {
      id: "heal",
      directives: [
        { type: "dispatch", target: "hp", op: "inc", value: 20 },
        { type: "notify", event: "healed", data: { amount: 20 } },
      ],
    },
  ],
  rules: [
    {
      id: "auto-heal",
      priority: 100,
      when: (ctx) => (ctx.get("hp") as number) < 30,
      then: [{ type: "action", id: "heal" }],
    },
    {
      id: "death-check",
      priority: 500,
      when: (ctx) => (ctx.get("hp") as number) <= 0,
      then: [
        { type: "dispatch", target: "status", op: "remove", value: "alive" },
        { type: "halt" },
      ],
    },
  ],
  events: [
    {
      id: "on-damaged",
      priority: 100,
      on: "damaged",
      then: [{ type: "dispatch", target: "score", op: "inc", value: 5 }],
    },
  ],
}, { env: { previousRealm: "forest", difficulty: "hard" } });

// 3. Ticks — executa PPP (Preparar → Processar → Propagar)
const step1 = rs.step();  // tick 0
const step2 = rs.step();  // tick 1

// 4. Leitura
rs.get("hp");        // valor atual
rs.get("hp", "max"); // field específico

// 5. Operações imperativas (entre ticks)
rs.invoke("heal");                          // invoca action diretamente
rs.dispatch("hp", "set", { value: 50 });    // muta estado diretamente
rs.emit("damaged", { amount: 10 });         // enfileira evento
rs.processEvents();                         // drena buffer de eventos

// 6. Observação
rs.on("step:complete", ({ tick, result }) => { /* ... */ });
rs.on("notify", ({ event, data }) => { /* ... */ });

// 7. Consulta — sempre disponível, sem guards
rs.getPhase();   // "created" | "booted" | "running"
rs.getTick();    // número do tick atual (-1 antes do primeiro step)
rs.isIdle();     // true entre ticks
rs.hasState("hp");
rs.hasAction("heal");
rs.hasRule("auto-heal");
rs.getView("combat");

Lifecycle

created ──boot()──→ booted ──step()──→ running ──step()──→ running ...
                         ↑                                      ↑
                    boot com erro                         step com erro
                    permanece created                     emite error + re-throw

| Transição | Quando | Validação | |-----------|--------|-----------| | created → booted | boot() com success | Boot sem erros | | created → created | boot() com erro | Boot com erros — não transiciona | | booted → running | Primeiro step() ou stepAsync() | Phase booted ou running | | running → running | Steps subsequentes | Phase booted ou running |

Physics — Leis do Mundo

Imutável após construção. Qualquer mudança = criar outro realm.

interface RealmPhysics {
  allowedDirectives?: DirectivePermissionEntry[];  // whitelist (exclusivo com blocked)
  blockedDirectives?: DirectivePermissionEntry[];  // blacklist
  limits?: Partial<FrameLimits>;                   // maxDepth, maxRules, maxDirectives
  maxEventPasses?: number;                         // drain loop limit (default: 10)
  async?: boolean;                                 // pausa intra-tick (default: false)
  enableViews?: boolean;                           // Pass 0 (default: false)
  enableEvents?: boolean;                          // Pass 2+ (default: true)
  temporal?: { stepSize?: number; cycleSize?: number };  // default: 1:1
  schemaValidator?: ISchemaValidator;              // validação de effects (opt-in, DI)
  imperatives?: boolean;                           // habilita invoke/dispatch/emit/processEvents (default: true)
}

System Config — Tuning

Tuning de processamento e injeção de comportamento.

interface RealmSystemConfig {
  mode?: "interpret" | "jit" | "auto";  // compilação (default: "auto")
  autoJitThreshold?: number;            // auto-promote (default: 8)
  analyzer?: boolean | AnalyzerConfig;  // ActionAnalyzer (default: false)
  debug?: boolean;                      // eventos internos (default: false)
  handlers?: Record<string, HandlerDefinition<RealmCtx>>;  // handlers customizados
}

Handlers Customizados

Handlers injetados via systemConfig.handlers são registrados no ActionEngine junto com os 6 built-in. O RS não sabe o que fazem — mesmo pattern de DI.

const rs = createRealmSystem({}, {
  handlers: {
    sandbox: mySandboxHandler,
    analytics: myAnalyticsHandler,
  },
});

Actions podem usar diretivas dos handlers customizados normalmente:

{ id: "create-child", directives: [{ type: "sandbox", target: "child-1" }] }

Boot Definitions

Todos os campos opcionais. Tipos definidos em @statedelta-axiom/contracts.

interface BootDefinitions<TCtx> {
  states?: StateDefinition[];
  actions?: ActionDefinition<TCtx>[];
  rules?: RuleDefinition<TCtx>[];
  events?: EventListenerDefinition<TCtx>[];
  eventDefinitions?: EventDefinition[];
  views?: ViewDefinition<TCtx>[];
}

StateDefinition — 7 types

{ id: "config", type: "record", data: { volume: 80, theme: "dark" } }
{ id: "hp", type: "counter", data: { value: 100, min: 0, max: 100 } }
{ id: "inv", type: "collection", data: [{ id: "sword", damage: 10 }] }
{ id: "queue", type: "list", data: ["task-1", "task-2"] }
{ id: "board", type: "matrix", data: { rows: 3, cols: 3, defaultValue: null } }
{ id: "status", type: "flags", data: { initial: ["alive"] } }
{ id: "phase", type: "statemachine", data: { initial: "menu", states: ["menu", "playing"], transitions: [{ from: "menu", to: "playing" }] } }

BootResult

result.success;              // true se zero erros
result.registered.states;    // IDs registrados
result.registered.actions;
result.registered.rules;
result.registered.events;
result.registered.views;
result.errors;               // [{ resource, id, code, message }]

Erros são isolados — falha em um recurso não impede os outros.

Boot Options

Segundo argumento opcional do boot(). Injeta dados persistentes no ctx.env().

rs.boot(definitions, {
  env: { score: 500, previousRealm: "forest" },  // birth data
});

Birth data é acessível via ctx.env("score") em qualquer tick. Persiste durante toda a vida do realm.

Effects — Input Externo Passivo

Effects são o mecanismo passivo de input externo. A App deposita dados, rules reagem no próximo tick. Key-value, um effect por tipo por tick.

// App deposita entre ticks
rs.applyEffect("user-input", { key: "space" });
rs.applyEffect("mouse", { x: 10, y: 20 });

// Rules consultam durante o tick
ctx.run("effects")  // → { "user-input": { key: "space" }, "mouse": { x: 10, y: 20 } }

// Effects morrem no fim do tick — per-run

Request — Realm Pede Input

A diretiva { type: "request", id: "..." } permite que o realm solicite input externo. O tick completa normalmente. O lock entra em vigor pro próximo step() — o System trava até que a App forneça o effect solicitado.

// Rule solicita input com JSON Schema opcional
{ type: "request", id: "user-choice", schema: {
  type: "object",
  properties: { option: { type: "string" } },
  required: ["option"],
}}

// App escuta o evento e responde
rs.on("request", ({ id }) => {
  showDialog().then(answer => rs.applyEffect(id, answer));
});

// Próximo step() trava se request não satisfeito
rs.step();  // → Error: System is locked. Pending requests: user-choice

applyEffect() satisfaz o request e deslocka. O effect fica disponível em ctx.run("effects") no tick seguinte.

Schema Validation (ADR-021)

Requests podem declarar um JSON Schema (Draft-07) que define o formato esperado do payload. A validação é opt-in — só acontece quando:

  1. O request declara schema
  2. O RS recebeu schemaValidator na physics (DI)

Se o payload falha na validação, applyEffect() lança erro e o lock é mantido — o request continua pendente.

import type { ISchemaValidator } from "@statedelta-axiom/contracts";

// Server injeta o validator (ex: wrapper de ajv)
const rs = createRealmSystem({
  schemaValidator: myAjvValidator,  // implementa ISchemaValidator
});

Sem schemaValidator, schemas são ignorados — zero overhead. Sem schema no request, o validator não é chamado — aceita qualquer payload.

Async Mode (ADR-024)

O RS suporta execução async via physics.async: true. Em async mode, o tick pode conter hooks async (directive hooks, rule hooks, event hooks) que são aguardados via await.

// Construção com async habilitado
const rs = createRealmSystem({ async: true, enableViews: true });
rs.boot(definitions);

// stepAsync() em vez de step()
const result = await rs.stepAsync();

Dual-method: step() vs stepAsync()

| Método | Quando usar | O que faz | |--------|------------|-----------| | step() | Realm sync (default) | Chama tickRunner.run() — sync | | stepAsync() | Realm async (physics.async: true) | Chama await tickRunner.runAsync() — async |

step() em async mode lança erro:

"Cannot call step() in async mode. Use stepAsync() instead."

stepAsync() funciona em ambos os modos — em realm sync, simplesmente aguarda Promises que resolvem imediatamente.

Validação de coerência no boot

Se hooks async são detectados mas physics.async não está habilitado, o boot lança erro:

"Async hooks detected but physics.async is not enabled."

Isso previne misconfiguration silenciosa — hooks async em realm sync causariam throw no evaluate() do RuleEngine/EventProcessor.

Na direção oposta, physics.async: true com hooks todos sync é válido — o realm declara capacidade async sem ter hooks async no momento.

Propagação transitiva

ActionEngine (directiveHooks async?)
  └── isAsync = true
        ├── RuleEngine herda
        ├── EventProcessor herda
        └── ViewEvaluator herda
              └── RS detecta no boot

Snapshot / Restore

Captura e restaura estado completo do realm. Não inclui definitions — concern da Sandbox/DSL layer.

// Captura após ticks
rs.step();
rs.step();
const snap = rs.snapshot();

// Mais ticks...
rs.step();
rs.step();

// Restaura — volta pro ponto do snapshot
rs.restore(snap);
rs.step(); // continua do ponto restaurado

O snapshot inclui: Store (todos os states + $transition), tick, phase, birth data (env), pending requests e pending effects.

StepResult

step() e stepAsync() retornam o mesmo tipo:

const result = rs.step();              // sync
const result = await rs.stepAsync();   // async — mesmo StepResult

result.tick;          // número do tick executado (0, 1, 2, ...)
result.runResult;     // resultado do PPP (ruleResult, eventPasses, etc)
result.halted;        // true se halt ocorreu
result.thrown;        // true se throw ocorreu
result.requests;      // requests emitidos neste tick ([{ id, schema? }])

RealmCtx — 4 Canais

O contexto que rules, views e ops recebem durante o tick.

ctx.get("hp")            // estado via Router → Store
ctx.get("hp", "max")     // field específico
ctx.get("combat:$pct")   // field de view (split por :)

ctx.run("tick")          // dados efêmeros do tick (tick, step, effects, etc)

ctx.env("score")         // birth data (imutável, setado no boot)

ctx.getDelta("hp")       // { previous: 100, changed: true }
// previous = início do tick anterior (pre-mutation)
// current = estado ao vivo (inclui mutations intra-tick)

Paths com : são splitados automaticamente: "combat:$pct"router.get("combat", "$pct"). Isso permite cross-view deps e acesso a fields de qualquer state type.

System Events

Canal de lifecycle events separado dos eventos internos do realm. EventEmitter type-safe próprio do RS.

// Lifecycle
rs.on("booted", ({ result }) => { /* boot completou */ });
rs.on("step:complete", ({ tick, result }) => { /* tick completou */ });
rs.on("notify", ({ event, data }) => { /* diretiva notify disparou */ });
rs.on("request", ({ id, schema }) => { /* realm pede input externo */ });
rs.on("error", ({ phase, error }) => { /* erro no boot ou step */ });

// Imperativas (ADR-043 — hooks pra registro/replay)
rs.on("imperative:invoke", ({ id, params, tick }) => { /* ... */ });
rs.on("imperative:dispatch", ({ stateId, op, params, tick }) => { /* ... */ });
rs.on("imperative:emit", ({ event, data, tick }) => { /* ... */ });
rs.on("imperative:processEvents", ({ eventsProcessed, tick }) => { /* ... */ });

// Unsubscribe
const unsub = rs.on("step:complete", listener);
unsub(); // para de ouvir

Operações Imperativas (ADR-038)

Ações diretas executadas entre ticks, quando o System está idle. Requerem: phase === "running", isIdle() === true, physics.imperatives !== false.

// Invoca action diretamente — eventos gerados ficam no buffer
const result = rs.invoke("heal", { target: "player" });

// Muta estado diretamente
rs.dispatch("hp", "set", { value: 50 });

// Enfileira evento — não drena imediatamente
rs.emit("damaged", { amount: 10 });

// Drena buffer de eventos (drain loop completo)
rs.processEvents();

Variantes async: rs.invokeAsync(), rs.processEventsAsync().

Eventos gerados por invoke() ficam no buffer — drenam via processEvents() ou no próximo step(). Cada operação emite system event (imperative:invoke, etc) pra registro pela lib superior.

Desabilitação via physics.imperatives: false — todas as imperativas lançam erro.

Consulta (ADR-039)

Métodos de leitura direto no RS. Sempre disponíveis, qualquer phase, sem guards. Delegam pros engines internos — zero estado novo, zero overhead.

// Lifecycle
rs.getPhase()          // "created" | "booted" | "running"
rs.getTick()           // -1 antes do primeiro step
rs.isIdle()            // true entre ticks
rs.isAsync()           // true se hooks async detectados
rs.getCompilationMode() // "interpret" | "jit"

// States
rs.getStateIds()       // ["hp", "config", ...]
rs.hasState("hp")      // true

// Actions
rs.getActionIds()      // ["heal", "damage", ...]
rs.hasAction("heal")   // true
rs.getActionDefinition("heal") // ActionDefinition | undefined

// Rules
rs.getRuleCount()      // 2
rs.hasRule("auto-heal") // true

// Events
rs.getEventDefinition("hit") // EventDefinition | undefined

// Views
rs.getViewCount()      // 1
rs.hasView("combat")   // true
rs.getView("combat")   // IViewRecord (dados computados, locked após step)
rs.getViewStateDeps()  // Set<string> — paths observados

Records de views ficam locked (read-only) após cada step. O evaluator faz unlock → compute → lock a cada tick. Tentativa de set() em view locked lança erro — protege contra mutação externa entre ticks.

Temporal

Subdivisão temporal via physics. Default 1:1 (tick === step, sem cycle).

{ temporal: { stepSize: 4, cycleSize: 10 } }

| Counter | Descrição | Acesso | |---------|-----------|--------| | tick | Global, sempre incrementa | ctx.run("tick") | | step | Incrementa a cada stepSize ticks | ctx.run("step") | | tickAt | Posição do tick no step (1..stepSize) | ctx.run("tickAt") | | cycle | Incrementa a cada cycleSize steps | ctx.run("cycle") | | stepAt | Posição do step no cycle (1..cycleSize) | ctx.run("stepAt") |

Factories — Uso em Testes

As duas fases de construção são funções puras exportáveis (ADR-053).

import {
  createSystemComponents,
  bootDefinitions,
} from "@statedelta-axiom/realm-system";

// Fase 1 — cria infraestrutura
const components = createSystemComponents({ enableViews: true });

// Fase 2 — popula realm
const result = bootDefinitions(components, {
  states: [{ id: "hp", type: "counter", data: { value: 100 } }],
  rules: [{ id: "r1", priority: 1, when: () => true, then: [...] }],
});

// Testar sem lifecycle, sem ticks
components.store.has("hp");           // true
components.ruleEngine.has("r1");      // true
components.actionEngine.invoke("heal", undefined, components.ctx);

Exports

// RS
import { createRealmSystem } from "@statedelta-axiom/realm-system";

// Factory
import {
  createSystemComponents,
  bootDefinitions,
} from "@statedelta-axiom/realm-system";
import type { SystemComponents } from "@statedelta-axiom/realm-system";

// Módulos auxiliares
import {
  createRealmCtx,
  createTransitionStore,
  createBuiltinHandlers,
  createRequestBridge,
  computeTemporalEnv,
  buildRunEnv,
  initObservedPaths,
  captureObservedValues,
  EventEmitter,
} from "@statedelta-axiom/realm-system";

// Types
import type {
  RealmPhysics,
  RealmSystemConfig,
  SystemPhase,
  StepResult,
  RequestEntry,
  RealmSnapshot,
  RealmCtx,
  DeltaInfo,
  BootOptions,
  SystemEvents,
  IRealmSystem,
  TransitionStore,
  TemporalCounters,
  IEventEmitter,
} from "@statedelta-axiom/realm-system";

Arquitetura

Para internals técnicos, ver docs/ARCHITECTURE.md.

Para decisões arquiteturais (59 ADRs), ver docs/DECISIONS.md.

Licença

MIT