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

@_noobcoder007/rule-graph-editor

v1.0.0

Published

Framework-agnostic web component to read, create, edit and visualize json-rules-engine rule documents as an interactive decision graph.

Readme

verbose-robot — <jre-rule-editor>

Un web component instalable como dependencia npm, agnóstico de framework, para leer, crear y editar documentos de reglas compatibles con el formato de json-rules-engine (facts, condiciones anidadas all/any/not, y el event como resultado final), visualizados como un árbol/grafo de decisión interactivo.

  • Añadir un nuevo nodo de decisión (condición o grupo all/any/not) con un botón "+".
  • Editar los facts del documento como una lista de filas (una por fact, autoguardado al salir del campo) o, para pegar/importar varios de golpe, con un editor JSON en bloque plegable.
  • Una capa de lint semántico resalta en el propio grafo ambigüedades y contradicciones (grupos vacíos, not con más de un hijo, condiciones contradictorias, facts sin declarar, tipos de valor incompatibles con el operador, event.type ausente) antes de que rompan la regla.
  • Construido con Lit + D3 como Custom Elements estándar (Shadow DOM) — funciona en HTML plano, React, Vue, Angular o cualquier otro entorno.

Instalación

npm install @_noobcoder007/rule-graph-editor

Publicado en el registro público de npm bajo el scope personal de la cuenta que lo mantiene.

Uso en HTML plano

<script type="module" src="/node_modules/@_noobcoder007/rule-graph-editor/dist/rule-graph-editor.js"></script>

<jre-rule-editor id="editor" layout="horizontal" style="height: 560px; display:block;"></jre-rule-editor>

<script type="module">
  const editor = document.getElementById('editor');

  editor.setDocument({
    $schemaVersion: 1,
    facts: { age: 20, country: 'US' },
    rules: [
      {
        name: 'eligibility-rule',
        conditions: {
          all: [
            { fact: 'age', operator: 'greaterThanInclusive', value: 18 },
            { any: [
              { fact: 'country', operator: 'equal', value: 'US' },
              { fact: 'country', operator: 'equal', value: 'CA' },
            ] },
          ],
        },
        event: { type: 'eligible', params: { message: 'User is eligible' } },
      },
    ],
  });

  editor.addEventListener('jre:change', (e) => {
    console.log('documento actualizado', e.detail.document, 'origen:', e.detail.source);
  });

  editor.addEventListener('jre:lint', (e) => {
    console.log('problemas detectados', e.detail.warnings);
  });
</script>

Uso en React

import { useEffect, useRef } from 'react';
import '@_noobcoder007/rule-graph-editor';
import type { RuleEditorElement, RuleChangeDetail } from '@_noobcoder007/rule-graph-editor';

function RuleEditor({ document, onChange }: { document: RuleDocument; onChange: (doc: RuleDocument) => void }) {
  const ref = useRef<RuleEditorElement>(null);

  useEffect(() => {
    ref.current?.setDocument(document);
  }, [document]);

  useEffect(() => {
    const el = ref.current;
    const handler = (e: Event) => onChange((e as CustomEvent<RuleChangeDetail>).detail.document);
    el?.addEventListener('jre:change', handler);
    return () => el?.removeEventListener('jre:change', handler);
  }, [onChange]);

  return <jre-rule-editor ref={ref} style={{ display: 'block', height: 560 }} />;
}

Vue: igual que arriba, pero hay que decirle a Vue que jre-* son custom elements nativos y no un componente Vue sin registrar, o el compilador lanzará un warning/error:

// vite.config.js
export default {
  plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('jre-') } } })],
};

Angular: hay que añadir CUSTOM_ELEMENTS_SCHEMA al módulo/componente que use <jre-rule-editor>:

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@Component({ /* ... */, schemas: [CUSTOM_ELEMENTS_SCHEMA] })

En ambos casos, el documento se pasa igual: importar el paquete (registra los custom elements como efecto secundario), usar <jre-rule-editor> en la plantilla, pasar el documento vía .document/setDocument() con una referencia al elemento (ref/@ViewChild), y escuchar los eventos jre:* con addEventListener o el binding de eventos nativo del framework (@jre:change en Vue, (jre:change) no funciona en Angular por el : — usar addEventListener vía ElementRef en su lugar).

El RuleDocument

json-rules-engine no define un esquema para los facts (son valores/funciones en tiempo de ejecución), así que el componente usa un envelope propio, serializable, donde rules es 100% compatible byte a byte con lo que espera Engine.addRule():

interface RuleDocument {
  $schemaVersion: 1;
  facts: Record<string, unknown>;       // valores de muestra usados para visualizar/evaluar
  rules: RuleProperties[];              // idéntico al formato de json-rules-engine
}

interface RuleProperties {
  conditions: TopLevelCondition;        // { all: [...] } | { any: [...] } | { not: ... }
  event: { type: string; params?: Record<string, unknown> };
  name?: string;
  priority?: number;
}

Corte de alcance intencional (v1): las referencias a condiciones con nombre de json-rules-engine ({ condition: 'nombre' }) no están soportadas por el validador, el transformador a grafo, el lint ni la UI.

El editor solo manipula una regla a la vez — rules sigue siendo un array (para que el JSON sea idéntico al de json-rules-engine y no cambie la forma en que lo guardas en tu base de datos), pero siempre tiene longitud 0 (estado vacío, sin regla creada todavía) o 1. setDocument()/loadFromJSON() rechazan con jre:validation-error cualquier documento con 2 o más reglas, en vez de mostrar solo la primera y descartar el resto en silencio.

API pública de <jre-rule-editor>

Propiedades / atributos

| Nombre | Tipo | Descripción | |---|---|---| | document | RuleDocument | Getter/setter del documento completo (equivalente a getDocument()/setDocument()) | | readonly | boolean (atributo reflejado) | Desactiva "+"/editar/eliminar; el grafo queda solo-lectura (pan/zoom siguen activos) | | layout | 'horizontal' \| 'vertical' | Orientación del árbol | | theme | 'light' \| 'dark' \| 'auto' (atributo reflejado, por defecto 'auto') | 'auto' sigue el prefers-color-scheme del sistema/navegador; 'light'/'dark' fuerzan una paleta sin importar la preferencia del SO |

Métodos

getDocument(): RuleDocument                         // clon profundo del estado actual
setDocument(doc: RuleDocument): ValidationResult     // valida; si es inválido, dispara jre:validation-error y no cambia nada
loadFromJSON(json: string): ValidationResult          // JSON.parse + setDocument
exportJSON(pretty?: boolean): string
createRule(rule?: Partial<RuleProperties>): void   // crea la única regla del documento; no-op con jre:validation-error si ya existe una
clearRule(): void                                   // quita la regla y vuelve al estado vacío
validateDocument(doc?: RuleDocument): ValidationResult
lintDocument(doc?: RuleDocument): LintWarning[]

Eventos (bubbles: true, composed: true)

| Evento | detail | Cuándo | |---|---|---| | jre:change | { document, source } | Tras cualquier edición aceptada (source: condition|fact|event|rule|import) | | jre:lint | { warnings: LintWarning[] } | Justo después de cada jre:change, con el resultado del análisis semántico | | jre:node-select | { path?, node } | Al hacer clic en cualquier nodo del grafo | | jre:validation-error | { errors: ValidationError[] } | Al intentar cargar un JSON con forma inválida (incluye traer más de 1 regla); el documento anterior se conserva |

Theming: variables CSS (--jre-node-fill, --jre-node-stroke, --jre-edge-color, --jre-accent, --jre-severity-error, …) y partes ::part(root|header|graph|side|canvas) para restylear desde fuera del shadow DOM sin ejectar el componente.

El componente hereda el tema de la aplicación anfitriona por dos vías, combinables:

  1. Automático: con theme="auto" (el valor por defecto), sigue el prefers-color-scheme del navegador/SO — si el usuario tiene su sistema en modo oscuro, el editor se ve oscuro sin que la app tenga que hacer nada. theme="light" / theme="dark" fuerzan una paleta concreta sin importar el sistema.
  2. Explícito: cualquier app puede sobreescribir variables CSS individuales en <jre-rule-editor> (o más arriba en la página) — por ejemplo jre-rule-editor { --jre-accent: #ff5722; --jre-font: 'Inter', sans-serif; } — y esos valores ganan siempre sobre los del tema claro/oscuro por defecto. --jre-font por defecto es inherit, así que si no defines nada el componente toma la tipografía que ya esté heredando de tu página.

Los valores por defecto de los tokens (--jre-*) solo se declaran una vez, en <jre-rule-editor> — los subcomponentes internos (<jre-graph-view>, <jre-facts-panel>, …) únicamente consumen esas variables en vez de redeclararlas, para que la herencia a través de sus shadow roots internos funcione correctamente.

En el demo (npm run dev) hay un selector "Tema" con auto/claro/oscuro y una opción "Marca personalizada" que simula una app con su propio design system, sobreescribiendo varios tokens a la vez (--jre-accent, --jre-font, --jre-node-fill-group, …) con una paleta y tipografía completamente ajenas — ver demo/style.css (.brand-demo) para el ejemplo exacto.

Apps con su propio design system

Si la app anfitriona ya tiene su propia paleta/tema, no hace falta nada especial de nuestro lado — se resuelve igual que el punto 2 de arriba, mapeando los tokens del design system existente a los --jre-*:

/* si el design system ya expone sus propios tokens como variables CSS */
jre-rule-editor {
  --jre-accent: var(--brand-primary);
  --jre-bg: var(--brand-surface);
  --jre-border: var(--brand-border);
  --jre-text: var(--brand-foreground);
  --jre-severity-error: var(--brand-danger);
  --jre-font: var(--brand-font-family);
}

Si el design system NO expone variables CSS (p. ej. tokens de Sass compilados o un tema de JS tipo styled-components/MUI), se escriben los valores literales una sola vez en ese mismo bloque — funciona igual, solo que sin el nivel extra de indirección.

Cobertura actual de tokens: colores (fondo/superficie/borde/texto/acento/severidades) y tipografía (--jre-font). Espaciado y radios de borde todavía están fijos en el CSS de cada componente, no tokenizados — si tu design system también necesita controlar eso, se puede añadir.

Nota sobre diálogos nativos: todo diálogo modal del editor (formulario de nodo, confirmaciones de borrado) usa <dialog> propio estilizado con estos mismos tokens — deliberadamente no se usa window.confirm()/alert()/prompt() en ningún punto, porque esos son diálogos nativos del sistema operativo que ignoran cualquier CSS y romperían la consistencia visual con el resto del tema.

Inteligencia del grafo (anti-ambigüedad)

Además de validar la forma del JSON, src/core/lint.ts analiza el documento tras cada cambio y ancla cada problema al nodo exacto del grafo (borde rojo = error, ámbar = warning, azul = info; el resumen de la cabecera muestra el total):

| Código | Severidad | Qué detecta | |---|---|---| | not-arity | error | Un grupo not con 0 o más de 1 hijo (red de seguridad para JSON importado/pegado a mano) | | empty-group | warning | all/any vacío (all([]) es true, any([]) es false) | | unknown-fact | warning | Una condición referencia un fact no declarado en facts | | contradictory-conditions | error | Dos condiciones sobre el mismo fact, dentro del mismo all, que nunca pueden cumplirse a la vez | | duplicate-condition | info | Dos condiciones hermanas idénticas | | operator-value-mismatch | warning | El tipo de value no encaja con lo que espera el operador | | missing-event-type | error | Una regla no tiene event.type |

Es una heurística documentada, no un solver de restricciones completo. Además, la UI aplica guardarraíles proactivos: el selector de operador cambia el widget de value según el tipo esperado, un fact nuevo escrito en una condición se crea automáticamente en facts, un grupo not nunca puede tener más de un hijo, y borrar un fact usado por alguna condición pide confirmación.

Desarrollo

npm install
npm run dev        # demo en HTML plano (sin framework) en http://localhost:5173
npm test           # Vitest: mutaciones, lint, validación, y round-trip contra el Engine real de json-rules-engine
npm run typecheck
npm run build       # dist/ ESM + UMD + .d.ts

El demo (demo/) permite: cargar el JSON de ejemplo o un archivo propio, crear un documento nuevo, exportar el JSON resultante, ver en vivo cada jre:change, y evaluar el documento contra el Engine real de json-rules-engine para confirmar que sigue siendo válido para la librería.

Integración en un proyecto externo (verificado)

npm run build && npm pack genera exactamente el tarball que se subiría a npm. Se probó instalando ese tarball (no una copia local del repo) en tres proyectos externos desechables, cada uno representando una forma real de consumir el paquete:

  1. HTML plano, sin bundler: <script type="module" src="/node_modules/@_noobcoder007/rule-graph-editor/dist/rule-graph-editor.js"> + <jre-rule-editor> en el HTML — npm install trae Lit y D3 automáticamente (17 paquetes), sin que el proyecto externo tenga que declararlos.
  2. App con bundler (Vite/webpack/React/Vue/Angular): import '@_noobcoder007/rule-graph-editor' con especificador "pelado" (sin ruta relativa) — Vite lo resuelve y pre-empaqueta correctamente vía el campo exports del package.json, igual que haría con cualquier paquete de npm.
  3. Proyecto TypeScript: import { RuleEditorElement, createEmptyDocument, mutations } from '@_noobcoder007/rule-graph-editor' type-checkea correctamente contra dist/index.d.ts, con los tipos completos (no any).

Notas de compatibilidad:

  • Depende de Custom Elements v1 + Shadow DOM + <dialog> nativo — navegadores evergreen modernos (Chrome/Edge/Firefox/Safari recientes); no soporta IE11.
  • lit y los paquetes d3-* van como dependencies normales (se instalan solos); json-rules-engine es peerDependency opcional (solo hace falta si además quieres ejecutar las reglas, no para leer/editar/visualizar).
  • El paquete aún no está publicado en el registro público de npm — mientras tanto se consume vía npm pack + npm install <tarball>.tgz, npm link, o una dependencia de git.