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-actions/analyzer

v0.6.1

Published

Static analysis layer for ActionEngine — capabilities, dependency graph, access control

Readme

@statedelta-actions/analyzer

Camada de análise estática para o ActionEngine. Graph queries, detecção de ciclos, inferência de capabilities, composition control e detecção de conflitos de declaração.


Filosofia

O engine é o V8. O analyzer é o TypeScript.

O ActionEngine é runtime puro: valida, armazena, compila, executa. Não sabe o que "capabilities", "ciclos" ou "access control" significam.

O ActionAnalyzer é o type checker externo: observa o engine (read-only), extrai fatos, valida contratos e expõe consultas. Nunca muta o engine.

Três modos de operação:

| Modo | O que usa | Analogia | |------|-----------|----------| | JS mode | Engine only | JavaScript puro — sem checagem | | TS mode | Engine + Analyzer | TypeScript — análise estática, graph queries | | TS strict | Engine + Analyzer + Composition manifest | strict: true — análise + composition control + contratos |

O consumer decide o nível de análise. O engine funciona sozinho. O analyzer é opt-in.


Instalação

pnpm add @statedelta-actions/analyzer

Peer dependencies: @statedelta-actions/core, @statedelta-actions/graph, @statedelta-actions/actions.


Início Rápido

import { createActionEngine } from "@statedelta-actions/actions";
import { createActionAnalyzer } from "@statedelta-actions/analyzer";

// 1. Criar engine
const engine = createActionEngine({ handlers });

// 2. Criar analyzer
const analyzer = createActionAnalyzer({ engine });

// 3. Wire lifecycle events
const unsubs = [
  engine.on("register",   (e) => analyzer.processRegistration(e)),
  engine.on("unregister", (e) => analyzer.processUnregistration(e)),
];

// 4. Usar normalmente — engine opera, analyzer observa
engine.register([
  { id: "heal", directives: [{ type: "state", path: "hp", value: 100 }] },
  { id: "combat", directives: [{ type: "invoke", action: "heal" }] },
]);

// 5. Consultar o analyzer
analyzer.capabilitiesOf("combat");  // Set { "invoke", "write" } (transitivo)
analyzer.dependenciesOf("combat");  // Set { "heal" }
analyzer.dependentsOf("heal");      // Set { "combat" }
analyzer.inCycle("heal");           // false

// 6. Cleanup
unsubs.forEach((fn) => fn());
analyzer.dispose();

Três Modos de Operação

JS mode — Engine only

const engine = createActionEngine({ handlers });
engine.register([...]);
engine.invoke("myAction", ctx);
// Sem análise. Runtime puro. Zero overhead.

Funciona: registration, invoke, JIT. Não funciona: ciclos, capabilities, composition control, declarations, dependency queries.

TS mode — Engine + Analyzer

const engine = createActionEngine({ handlers });
const analyzer = createActionAnalyzer({ engine });

// Wire events...
engine.on("register", (e) => analyzer.processRegistration(e));
engine.on("unregister", (e) => analyzer.processUnregistration(e));

engine.register([...]);

// Graph queries disponiveis
analyzer.capabilitiesOf("myAction");
analyzer.inCycle("myAction");
analyzer.getStats();

Tudo do JS mode + ciclos, capabilities transitivas, declarations, dependency queries.

TS strict — Engine + Analyzer + Composition Control

const analyzer = createActionAnalyzer({
  engine,
  accessManifest: {
    rules: [
      {
        from: ["write"],
        to: ["readonly"],
        effect: "deny",
      },
    ],
  },
});

// Wire events...
const result = analyzer.processRegistration(event);
result.accessViolations; // AccessViolation[] — edges que violam regras

Tudo do TS mode + composition control com manifest, validacao de edges em register-time.


Wiring

O analyzer não auto-subscribe. O consumer decide a estratégia de wiring (direto, batched, tick-boundary).

Dois lifecycle events:

| Evento | Método do Analyzer | Quando o engine emite | |--------|-------------------|----------------------| | "register" | processRegistration(e) | Após register() ou endBatch(), se houve registros | | "unregister" | processUnregistration(e) | Após unregister(), se a action existia |

const unsubs = [
  engine.on("register",   (e) => analyzer.processRegistration(e)),
  engine.on("unregister", (e) => analyzer.processUnregistration(e)),
];

// Cleanup
unsubs.forEach((fn) => fn());

Um listener por evento. Múltiplos listeners causam processamento duplicado.


Graph Queries

Todas delegam diretamente pro DependencyGraph. Zero lógica extra.

| Método | Retorno | Descrição | |--------|---------|-----------| | capabilitiesOf(id) | ReadonlySet<string> \| undefined | Capabilities resolvidas (own + transitivas) | | propertyOf<T>(id, name) | T \| undefined | Valor resolved de qualquer propriedade | | inferredOf<T>(id, name) | T \| undefined | Valor inferido (ignora declarations) | | dependenciesOf(id) | ReadonlySet<string> | Dependências diretas | | dependentsOf(id) | ReadonlySet<string> | Quem depende dessa action | | conflictsOf(id) | readonly DeclarationConflict[] | Conflitos inferred vs declared | | inCycle(id) | boolean | Se participa de ciclo | | getStats() | GraphStats | Estatísticas do grafo |

analyzer.capabilitiesOf("combat");
// Set { "invoke", "write" } — transitivo via heal

analyzer.propertyOf<number>("combat", "maxDepth");
// 1

analyzer.inferredOf<boolean>("action", "readonly");
// valor inferido, ignora declarations

analyzer.dependenciesOf("combat");  // Set { "heal" }
analyzer.dependentsOf("heal");      // Set { "combat" }
analyzer.inCycle("A");              // true/false

analyzer.conflictsOf("action");
// [{ property: "readonly", declared: true, inferred: false }]

analyzer.getStats();
// { totalActions, totalEdges, leafCount, branchCount, cycleCount, maxDepth, isolatedCount }

Acesso direto ao grafo: analyzer.graph (readonly DependencyGraph).


Composition Control

Controle de dependências arquiteturais baseado em tags com pattern matching. Avalia edges (source -> target) contra regras declarativas. Define quem pode depender de quem — dev tooling pra manter a arquitetura limpa.

from e to são NodeMatchers — avaliados contra:

  • from (source / quem invoca) → ownCapabilities ∪ tags do source
  • to (target / quem é invocado) → resolved capabilities ∪ tags do target (transitivo)

NodeMatcher aceita string, string[] (OR), ou { any | all | none: TagMatcher }. Tag patterns suportam glob ("state:*"), negação ("!write"), literal e listas.

const analyzer = createActionAnalyzer({
  engine,
  accessManifest: {
    rules: [
      // Actions com capability "write" não podem invocar actions taggeadas "protected-*"
      {
        from: ["write"],
        to: "protected-*",
        effect: "deny",
      },
      // Forma equivalente com any/all/none:
      // { from: { any: "write" }, to: { any: "protected-*" }, effect: "deny" }
    ],
  },
});

Violações aparecem no AnalysisResult.accessViolations. A action e registrada — o consumer decide a política.

setAccessManifest — Manifest mutável

// Aplica novo manifest em runtime (recompila + revalida tudo)
const result = analyzer.setAccessManifest({
  rules: [
    { from: ["read"], to: ["write"], effect: "deny" },
  ],
});
result.accessViolations; // Violações das edges existentes

// Desativa composition control (zero overhead)
analyzer.setAccessManifest(null);

Para detalhes de TagMatcher, NodeMatcher, AccessRule, pipeline de compilacao e semântica de matching, veja ARCHITECTURE.md.

Nota sobre nomenclatura: O código usa accessManifest, AccessRule, AccessViolation etc. Conceitualmente, isso e composition control — controle de dependências arquiteturais (quem pode depender de quem), não controle de acesso de usuario. ACL real (RBAC, autenticacao) e extensão futura, completamente separada. Ver ARCHITECTURE-VISION.md.


Tier Validation

Controle hierárquico por nível numerico. Actions com tier declarado participam de uma hierarquia: action com tier menor não pode invocar action com tier maior. Actions sem tier sao livres.

const analyzer = createActionAnalyzer({
  engine,
  tierValidation: true, // injeta tierPropagator + valida edges
});

// Wire events...
engine.on("register", (e) => analyzer.processRegistration(e));
engine.on("unregister", (e) => analyzer.processUnregistration(e));

engine.register([
  { id: "admin-action", directives: [...], tier: 500 },
  { id: "user-action", directives: [{ type: "action", id: "admin-action" }], tier: 100 },
]);

// result.tierViolations:
// [{ sourceId: "user-action", targetId: "admin-action", sourceTier: 100, targetTier: 500, message: "..." }]

Semântica

| Source tier | Target tier | Resultado | |------------|-------------|-----------| | undefined | undefined | Livre | | undefined | 500 | Livre | | 100 | undefined | Livre | | 100 | 100 | OK (igual) | | 100 | 500 | Violacao | | 500 | 100 | OK (higher → lower) |

undefined = livre. Actions sem tier não participam da validacao. Tier e declaração explicita de intenção — so tem efeito quando o dev declara.

Dois mecanismos complementares

| Mecanismo | O que detecta | Exemplo | |-----------|--------------|---------| | Edge validation | Invocacao direta com tier insuficiente | A(100) → B(500) | | DECLARATION_CONFLICT | Tier declarado menor que inferido da sub-árvore | A declara 100, sub-árvore exige 500 |

Edge validation pega violações diretas. DECLARATION_CONFLICT pega violações transitivas (A → B → C onde C exige tier alto). Juntos cobrem todos os cenarios.

Consulta de tier

// Tier inferido (max da sub-árvore, incluindo declarado próprio)
analyzer.propertyOf<number>("action-A", "tier"); // 500

// Tier declarado (so o que a action declarou explicitamente)
analyzer.graph.get("action-A")?.declarations.get("tier"); // undefined (não declarou)

Quando usar

  • Actions com tier: actions de dominio que participam de hierarquia arquitetural (admin, system, domain-specific).
  • Actions sem tier: utility actions (log, format, validate) — qualquer um pode chamar.
  • Rules: o RuleEngine pode injetar tier via priority. Com tierValidation: true, o analyzer valida que uma rule de priority 300 não invoca (direta ou transitivamente) actions que exijam tier > 300.

Async / Interactive Propagators (opt-in)

Habilita análise rica sobre dois conceitos do engine que afetam compilação: async (action invoca handler async transitivamente) e interactive (action invoca generator handler ou type:"pause" transitivamente).

Engine ja resolve compilação sozinho via mini-graph interno (ADR-026 do actions). Esses propagators no analyzer adicionam:

  1. DECLARATION_CONFLICT — action declara contrato público que contradiz inferência transitiva
  2. Queries semânticasanalyzer.propertyOf<boolean>(id, "async"), propertyOf<boolean>(id, "interactive")
  3. Composition rules — manifest pode bloquear edges baseadas nessas propriedades
import {
  asyncPropagator,
  interactivePropagator,
  DEFAULT_PROPAGATORS,
} from "@statedelta-actions/graph";

const analyzer = createActionAnalyzer({
  engine,
  propagators: {
    ...DEFAULT_PROPAGATORS,
    async: asyncPropagator,
    interactive: interactivePropagator,
  },
});

Sources (extract)

Propagator | Source local ---|--- async | capability async (handler.analyze marca) ou declaração async: true interactive | diretiva type: "pause" direta, capability interactive, ou declaração interactive: true

Ambos com contagion any_true — qualquer dep tendo a propriedade propaga.

DECLARATION_CONFLICT

Action assina contrato público que contradiz a inferência transitiva → conflito reportado.

engine.register([
  { id: "fetchUser", directives: [{ type: "fetch" }] },           // capability async
  { id: "syncFacade",
    directives: [{ type: "action", id: "fetchUser" }],
    declarations: { async: false } },                              // mente ao consumer
]);

analyzer.conflictsOf("syncFacade");
// [{ property: "async", declared: false, inferred: true }]

Mesmo padrão pra interactive. Consumer decide a política (rejeitar boot, warning, ignorar).

Composition rules sobre async/interactive

Manifest pode usar properties no matcher pra bloquear edges:

const analyzer = createActionAnalyzer({
  engine,
  propagators: { ...DEFAULT_PROPAGATORS, async: asyncPropagator },
  accessManifest: {
    rules: [
      {
        source: { tags: { include: ["query"] } },
        target: { properties: { async: true } },
        effect: "deny",
        reason: "query actions must not invoke async paths",
      },
    ],
  },
});

Independência do engine (ADR-027)

Engine não consulta analyzer. O mini-graph interno cobre compilação. Esses propagators sao puramente dev-tooling — opt-in, externo, simétrico. Engine continua autônomo sem analyzer.


sync() — Hot-Plug

O analyzer pode ser criado depois de actions ja registradas. sync() le o registry inteiro do engine e reconstrói a análise completa:

// Actions registradas antes do analyzer existir
engine.register([...]);

// Analyzer criado depois — perdeu os eventos
const analyzer = createActionAnalyzer({ engine });
const result = analyzer.sync(); // Le tudo: registry completo
// result: AnalysisResult

// Wire pra eventos futuros
engine.on("register", (e) => analyzer.processRegistration(e));
// ...

sync() é idempotente. Chamar duas vezes reconstrói do zero com resultado idêntico. Útil como escape hatch.


dispose()

Limpa estado interno (cache do validator, grafo). Chamadas subsequentes sao no-op.

// Cancelar listeners primeiro
unsubs.forEach((fn) => fn());

// Cleanup do analyzer
analyzer.dispose();

Referência de Configuração

interface ActionAnalyzerConfig<TCtx = unknown> {
  /** Engine pra observar (read-only). Obrigatorio. */
  engine: IAnalyzableEngine<TCtx>;

  /**
   * Grafo de dependências.
   * Se fornecido: usa direto (consumer controla propagators).
   * Se omitido: cria internamente com DEFAULT_PROPAGATORS.
   */
  graph?: DependencyGraph;

  /**
   * Propagators pro grafo.
   * So usado se `graph` não foi fornecido.
   * Default: DEFAULT_PROPAGATORS (capabilities, leaf, maxDepth).
   */
  propagators?: Record<string, Propagator<any>>;

  /**
   * Manifest de composition control (controle de dependências arquiteturais).
   * Se omitido: sem composition control (zero overhead).
   * Pode ser alterado em runtime via setAccessManifest().
   */
  accessManifest?: AccessManifest;
}

| Config | Comportamento | |--------|---------------| | graph fornecido | Usa direto. Consumer controla propagators. | | graph omitido, propagators fornecido | Cria graph com propagators fornecidos. | | graph e propagators omitidos | Cria graph com DEFAULT_PROPAGATORS. |


Exports

// Types: Analyzer
IActionAnalyzer, ActionAnalyzerConfig, IAnalyzableEngine,
AnalysisResult, AnalysisWarning, AnalysisConflict,
UnregistrationResult

// Types: Lifecycle Events
RegisterEvent, UnregisterEvent

// Factory
createActionAnalyzer

// Analysis (pure function)
analyzeAction

// Composition Control (code still uses "access" naming)
AccessValidator, AccessOrchestrator,
AccessOrchestratorDeps, TagMatcher, NodeMatcher,
AccessRule, AccessManifest, AccessViolation, AccessValidationResult

// Composition Matching (pure functions — advanced usage / testing)
compilePattern, compileTagMatcher, compileNodeMatcher,
compileRule, compileManifest

Licença

MIT


Para arquitetura interna, detalhes de implementacao e decisoes de design, veja ARCHITECTURE.md.