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 🙏

© 2025 – Pkg Stats / Ryan Hefner

sistema-lenguaje-firewall

v1.0.6

Published

Centralizar lógica (condicional) entorno a eventos usando un pequeño lenguaje que intercala JavaScript.

Downloads

190

Readme

sistema lenguaje firewall

Centralizar lógica (condicional) entorno a eventos usando un pequeño lenguaje que intercala JavaScript.

Sitios oficiales

Instalar

Puedes bajártelo con git así:

git clone https://github.com/allnulled/sistema-lenguaje-firewall.git .

También puedes descargártelo mediante npm así:

npm install -s sistema-lenguaje-firewall

Uso

Para usar la librería:

// 1. Crear el firewall:
const firewall = require("sistema-lenguaje-firewall").crear();

// 2. Cargar y/o escuchar fichero:
const ruta =__dirname + "/firewall.fwl";
await firewall.cargar_fichero(ruta);
firewall.escuchar_fichero(ruta);

// 3. Emitir eventos:
await firewall.emitir("evento.id", "parametros");

Lenguaje

El script que centraliza esta lógica sigue su propia sintaxis. He aquí algunos ejemplos de uso:

[ rule 87 ]
on event { * } then {
    anytime [[ // load_everything() ]]
    if not [[ true ]] or [[ false ]] and (
        ([[ false ]] or [[ false ]]) or
        ([[ false ]] or [[ true ]])
    ) then [[ 
        // await go();
    ]]
    if [[ false ]] or [[ false ]] and (
        [[ false ]] or [[ false ]]
    ) then [[ 
        // await go();
    ]]
}

Puedes usar la sintaxis en castellano:

[ Regla X ]
en evento { * } entonces {
    siempre [[ // cargar_todo() ]]
    si no [[ true ]] o [[ false ]] y (
        ([[ false ]] o [[ false ]]) o
        ([[ false ]] o [[ true ]])
    ) entonces [[ 
        // await go();
    ]]
    si [[ false ]] o [[ false ]] y (
        [[ false ]] o [[ false ]]
    ) entonces [[ 
        // await go();
    ]]
}

O puedes usar otras sintaxis para los textos:

on event { * } then {
    anytime {{ // await cargar_todas_las_cosaj() }}
    if (not {{ true }} and not {{ true }} and {{ false }}) then {{
        // stay_high()
    }}
}

on event { * } then {
    anytime {{ }}
}

on event { * } then {{ // load_everything() }}

on event { * } then {{
    console.clear();
    console.log("[TRACE] A las " + (new Date()).toString() + "\n[TRACE] Evento emitido: " + evento);
    console.log("[TRACE]", parametros);
}}

Puedes usar también otros contenedores de JavaScript/texto como:

  • @{ y }
  • {{ y }}
  • {{{ y }}}
  • {{{{ y }}}}
  • [: y :]
  • [[ y ]]
  • [[[ y ]]]
  • [[[[ y ]]]]

Se reserva el uso de { y } para los bloques then/entonces que continúen la sintaxis propia del firewall (no JavaScript, vamos).

Los selectores permiten que:

  • Mientras que insertar no será llamado al insertar.muchos o insertar.uno.
  • Ocurre que insertar.* sí será llamado al insertar.muchos o insertar.uno.

Pero en esencia, se trata de:

  • Crear una máquina firewall
  • Cargarla/Vincularla con un script en *.fwl
  • Esparcir emisiones de eventos por toda la aplicación

Lo que se consigue de esta manera es, repito:

  • CENTRALIZAR la lógica más sensible de la aplicación
  • ORGANIZARLA según eventos para un uso eficiente
    • no tener miles de eventos juntos, sin orden ni criterio
    • no tener que procesar un script para saber qué hacer de nuevas todo el rato
  • CLARIFICAR un punto crítico en muchas aplicaciones
    • ...me refiero a la "lógica de negocio".
  • PERMITIR LA MANIPULACIÓN del script de negocio
    • Con operaciones mágicas del firewall
    • Usando las referencias que se pueden aprovechar del lenguaje

No son muchas ventajas, pero alguna de ellas puede aumentar la mantenibilidad de forma crítica, como centralizar la lógica sensible y clarificar la información.

Detalles del lenguaje

El lenguaje acepta sentencias, como todos los lenguajes.

Sentencias globales

El lenguaje capta 1 tipo de sentencia: el registro de eventos. Puede repetirse cuantas veces sean.

Sentencias de «registro de eventos»

Este, por ejemplo, es el evento genérico. Si usamos el *, estaremos inyectando código en todos los eventos de la máquina.

[ rule whatever ]
on event { * } then {
    
}

El título rule whatever te será una útil referencia para más adelante, en usos avanzados de la máquina. De momento, es un simple nombre que se le puede dar a ese evento. Pero es totalmente opcional y ahorrable.

Hay otros detalles sobre los identificadores de eventos:

  • Tienen espacios de nombre. Por defecto es .. Todos están separados por el caracter que hay en firewall.configuraciones.separador_de_ambito. Por defecto, es ..
    • Los espacios de nombres permiten jugar con el asterisco en los selectores.
    • Por ejemplo, si vas a hacer una API, puedes prefijarla con un nombre, y lanzar eventos en toda la API solo con: miapi.*
  • Tienen separador de eventos. Por defecto es ,. Está en firewall.configuraciones.separador_de_eventos. Con esto, puedes crear reglas que apunten a varios eventos a la vez. Por ejemplo:
on events {
    rest.insert.many,
    rest.update.many,
    rest.delete.many
} then {
    
}

En este ejemplo se está apuntando a varios selectores al mismo tiempo, y es correcto.

Pues dentro de esta sentencia, podemos encontrar 3 tipos de sentencia:

  • Siempre. Los bloques de siempre siempre se ejecutan. Solo admite bloques de JavaScript.
  • Proceso. Los bloques de procesos pueden interrumpirse con un {{ break nombre_de_proceso; }} de JavaScript. Permiten continuar la sintaxis solamente.
  • Condicional. Los bloques condicionales pueden definir unas condiciones y decidir qué viene después, si más sentencias tipo así, o un bloque de JavaScript a pelo.

Sentencia de «siempre»

Un ejemplo de sentencia de siempre es muy sencillo.

siempre {{ console.log("que pasa por aquí, imprimirá esto") }}

anytime {{ console.log("que pasa por aquí, imprimirá esto") }}

Sentencia de «en proceso»

Un ejemplo de sentencia de proceso es muy sencillo.

en proceso x: {
    siempre {{ console.log("Empieza proceso x") }}
    siempre {{ break x; }}
}

in process y: {
    siempre {{ console.log("Starts process y") }}
    siempre {{ break y; }}
}

Sentencia de «condicional»

Un ejemplo de sentencia de condicional podría ser este:

si {{ condicion(1) }} y {{ condicion(2) }} o (
    {{ condicion(3) }} y {{ condicion(4) }}
) entonces {{
    console.log("OK 1")
}} o si ({{ condicion(5) }} o {{ condicion(6) }}) y (
    {{ condicion(7) }} o {{ condicion(8) }}
) entonces {{
    console.log("OK 2")
}} o si no entonces {{
    console.log("NO OK");
}}

if {{ condicion(1) }} and {{ condicion(2) }} or (
    {{ condicion(3) }} and {{ condicion(4) }}
) then {{
    console.log("OK 1")
}} o si ({{ condicion(5) }} o {{ condicion(6) }}) y (
    {{ condicion(7) }} o {{ condicion(8) }}
) entonces {{
    console.log("OK 2")
}} o si no entonces {{
    console.log("NO OK");
}}

Piensa que el entonces puedes continuarlo con bloques de JavaScript o con sintaxis normal. Por ejemplo:

if {{ condicion(1) }} then {
    in process one: {
        if {{ condicion(2) }} or {{ condicion(3) }} or {{ condicion(4) }} then {
            anytime {{ break one; }}
        } else if {{ condicion(5) }} or {{ condicion(6) }} then {
            anytime {{ break one; }}
        } else {
            anytime {{ throw new Error("No se puede"); }}
        }
    }
}

La API

En esta sección se detalla información sobre la API.

El constructor Firewall.crear(configuraciones)

Cuando importamos la API, nos devuelve una clase: Firewall.

const Firewall = require("sistema-lenguaje-firewall");

Para crear instancias con esa clase, podemos usar el método estático Firewall.crear(configuraciones).

Firewall.crear({
    // Aquí van las configuraciones.
})

El parámetro configuraciones que se le pasa, este objeto, puede sobreescribir el estado por defecto de las configuraciones.

Este es el estado por defecto de las configuraciones:

{
    tracear: true,
    ambito: this,
    separador_de_eventos: ",",
    separador_de_ambito: ".",
    globales: {},
    precargas: [],
}

La propiedad this.configuraciones.separador_de_ambito

El separador de ámbito es un texto que se usa para separar los ámbitos de un identificador de eventos. Así, decimos que el evento rest.actualizar.muchos tiene 3 ámbitos: rest, actualizar, y muchos. Esto permite el juego del * (asterisco) para seleccionar eventos, como hemos explicado antes.

Se usa en la sentencia de registrar eventos así:

on events {
    rest.insertar.muchos,
    rest.insertar.muchos.*,
    rest.actualizar.muchos,
    rest.actualizar.muchos.*,
    rest.eliminar.muchos,
    rest.eliminar.muchos.*,
} then {

}

Por defecto es . (un punto).

La propiedad this.configuraciones.separador_de_evento

El separador de eventos es un texto que se usa para separar los eventos de un identificador de eventos. Así, por ejemplo, podemos referirnos a diferentes eventos en una misma regla. El ejemplo anterior de this.configuraciones.separador_de_ambito es suficientemente ilustrativo.

Por defecto es , (una coma).

La propiedad this.configuraciones.tracear

Esta propiedad es un booleano que indica si se tienen que imprimir los traceos o no.

Por defecto es true.

La propiedad this.configuraciones.ambito

Esta propiedad es usada para hacer funcion_de_evento.bind(this.configuraciones.ambito) con todos los eventos que registremos en el firewall.

Por defecto es this (la instancia de firewall misma).

Por tanto, todos los eventos encontrarán en el this a la instancia de firewall por defecto.

La propiedad this.configuraciones.globales

Otra cosa a tener en cuenta es que si al crear el Firewall le pasas algo así en la propiedad de globales:

const firewall = Firewall.crear({
    globales: {
        nombre_de_funcion() {
            // Código de función
        },
        comprobar_una_cosa() {},
        comprobar_otra_cosa() {},
        comprobar_otra_cosa_mas() {},
        ejecutar_una_cosa() {},
        ejecutar_otra_cosa() {},
        ejecutar_otra_cosa_mas() {},
        no_hacer_nada() {}
    }
});

...entonces consigues tener nombre_de_funcion y las otras como variables del ámbito en el que se ejecuta el evento. No polucionan el espacio global de nombres, pero sí son extraídas del objeto this.configuraciones.globales y desacopladas de él en código. Esto significa que en este objeto globales no deben ir nombres de propiedades que no puedan ser usadas como nombre de variable en JavaScript. Y si lo haces bien, luego puedes hacer cosas así:

on event {*} then {
    if {{ comprobar_una_cosa() }} then {{ ejecutar_una_cosa() }}
    else if {{ comprobar_otra_cosa() }} then {{ ejecutar_otra_cosa() }}
    else if {{ comprobar_otra_cosa_mas() }} then {{ ejecutar_otra_cosa_mas() }}
    else {{ no_hacer_nada() }}
}

La propiedad this.configuraciones.precargas

Esta propiedad permite especificar una lista de rutas de ficheros que quieres que se usen cuando hagas:

await firewall.precargar_estado();

Que es una llamada implicada en la función:

await firewall.cargar_fichero(ruta);

Aunque no será llamada si usas el segundo parámetro así:

await firewall.cargar_fichero(ruta, { precargar: false }); // Por defecto está en true en «cargar_fichero»

Esta propiedad no está implicada en la llamada:

firewall.cargar_script(texto);

Esto se debe a que al precargar_estado se rompería la sincronía del método cargar_script, y no es deseable. Si quieres usar la precarga en combinación con cargar_script, puedes usarlo por tu cuenta combinándolo con el método precargar_estado que, a diferencia de cargar_script, es asíncrono.

Ten en cuenta que los eventos de la precarga se incrustarán en firewall.eventos pero el firewall.estado no mostrará el AST (Abstract Syntax Tree) de ninguno de esos ficheros, sino del último fichero que es el que usas cuando llamas a firewall.cargar_fichero(este_fichero_digo).

La API más en profundidad

Principalmente, la API se usa así:

const firewall = require("sistema-lenguaje-firewall").crear({ precargas: ["auth.fwl"] });
await firewall.cargar_fichero("custom.fwl");
await firewall.emitir("evento", "parametros");

Aquí, en 3 líneas, estarías preparando lógica fija (precargas), lógica variable (custom.fwl) y emitiendo un evento ya.

Pero hay más usos que pueden interesarte, sin meterse dentro de la API de "bajo nivel" de la librería, que son más como utilidades que usa la de "alto nivel".

Y esto es lo que querría explicar en esta sección.

Crear una regla en vivo con la API

Puedes crear nuevas reglas para el firewall sin dejar de usarlo así:

await firewall.insertar_regla(codigo); // usará la ruta del último fichero cargado
await firewall.insertar_regla(codigo, fichero); // usará la ruta proporcionada en fichero

Esto hará que:

  1. Lea el fichero
  2. Interprete el fichero
  3. Cambie el código - Para añadir la regla en este caso
  4. Sobreescriba el fichero
  5. Resetee el estado y los eventos del firewall
  6. Precargue los ficheros (recuerda: de firewall.configuraciones.precargas)
  7. Cargue nuevamente el fichero

Y así actualice su estado en función de los cambios aplicados.

Cambiar una regla en vivo con la API

Puedes cambiar reglas del firewall sin dejar de usarlo así:

await firewall.cambiar_regla(id_de_regla, codigo); // usará la ruta del último fichero cargado
await firewall.cambiar_regla(id_de_regla, codigo, fichero); // usará la ruta proporcionada en fichero

Esto hará que:

  1. Lea el fichero
  2. Interprete el fichero
  3. Cambie el código - Para cambiar la regla en este caso
  4. Sobreescriba el fichero
  5. Resetee el estado y los eventos del firewall
  6. Precargue los ficheros (recuerda: de firewall.configuraciones.precargas)
  7. Cargue nuevamente el fichero

Y así actualice su estado en función de los cambios aplicados.

Eliminar una regla en vivo con la API

Puedes eliminar reglas para el firewall sin dejar de usarlo así:

await firewall.eliminar_regla(codigo); // usará la ruta del último fichero cargado
await firewall.eliminar_regla(codigo, fichero); // usará la ruta proporcionada en fichero

Esto hará que:

  1. Lea el fichero
  2. Interprete el fichero
  3. Cambie el código - Para eliminar la regla en este caso
  4. Sobreescriba el fichero
  5. Resetee el estado y los eventos del firewall
  6. Precargue los ficheros (recuerda: de firewall.configuraciones.precargas)
  7. Cargue nuevamente el fichero

Y así actualice su estado en función de los cambios aplicados.

Recargar el firewall

Para recargar todo: las precargas y el fichero interactuable (mediante estos métodos, que acabo de explicar, de: insertar_regla, cambiar_regla, eliminar_regla), hay un método al uso, usa asincronía porque tiene que leer ficheros, en teoría:

await firewall.recargar();

Tienes que haber hecho antes esto para que funcione:

await firewall.cargar_fichero("firewall.fwl");

Vincular un fichero al firewall

Para sincronizar el estado de un fichero automáticamente con el firewall, lo cual no siempre es la mejor opción (es menos costoso firewall.recargar() y ya), puedes usar este método, que por debajo usa la librería chokidar:

firewall.escuchar_fichero(fichero);

De esta forma, si cambias el fichero, y el código está bien formado, se cambia el estado del firewall automáticamente.

Conclusiones

Esta librería y lenguaje surge por la demanda de aliviar tensión en un punto crítico de muchas aplicaciones.

La lógica de negocio, cuando las cosas se empiezan a complicar, se vuelve un punto oscuro, donde pasan muchas cosas, pero no sabes, todo está junto, con código.

Esta librería INVITA y FACILITA buenas prácticas a la hora de desarrollar esta lógica de negocio:

  • Mayor desacoplamiento de código. Al interesar tener las funciones separadas y accesibles, el lenguaje invita a que separes y definas claramente el código que quieres implicar en esta centralización de la lógica (de seguridad sobre todo, por cierto, enfocado a eso sobre todo, por eso lo he llamado firewall aunque puede usarse para muchas cosas más). El juego de las globales, por ejemplo, ya invita a que separes el código que quieres implicar en esta lógica.
  • Mayor desacoplamiento entre causas (condiciones) y consecuencias (instrucciones). Al solo disponer de sentencias escasas, el lenguaje ya invita a que utilices condicionales y procesos interrumpibles para guiar al flujo de ejecución por donde interesa en la aplicación. Esto invita a su vez a romper la lógica de negocio en 2 categorías: condiciones (ifs) e instrucciones (thens).
  • Mayor granularidad. Este desacoplamiento trae consigo la ventaja de granular más el código, y así reaprovechar el código de condiciones e instrucciones para otras ocasiones.
  • Mayor claridad del código. Cuando entras en un script así, ya sabes qué tipo de lógica vas a encontrar. Y eso permite que puedas usar un lenguaje que aporta mayor claridad al expresar las intenciones que tienes para con el programa. Por eso surge el lenguaje, esta es la principal razón por la que lo construí.
  • Menor ofuscación del código. Si te fijas, es una sintaxis muy seca. Podría permitir muchísimas más cosas, podría ampliarse a lenguaje de propósito general. Pero esto permitiría que la ofuscación del código proliferase. De esta forma, nos centramos en lo que interesa, y apartamos lo que no.
  • Mayor organización. Con esto va a estar tirado localizar la lógica de la seguridad, buscar en ella, y encontrar rápidamente los fallos de seguridad.
  • Seguridad más asequible. No es lo mismo tener un sistema de condicionales esparcido por todo el proyecto, que tener la lógica sensible centralizada en 1 solo sitio. Esto permite conseguir seguridad más fácilmente, así como llegar más lejos, sin tanta complejidad que luego además te quitará tiempo.

El proyecto es muy parecido a otros que he hecho, del tipo gestor de hooks. La diferencia de este es que ya lo he orientado a clarificar condicionales intrincados que puedan complicar la lógica de seguridad sobre todo, donde tienes que tener en cuenta permisos y cosas así.

Me gusta enfocarlo como una máquina que va a facilitar que puedas utilizar sistemas de autorización, permisos y seguridad así, de una forma ya más o menos guiada, pero sin quitarle potencial práctico tampoco. Tú preparas esa máquina (con funciones, las globales), cargas el estado (con texto) o vinculas el estado (a un fichero), y de ahí utilizas la máquina para dispersar la lógica de seguridad por tu programa. De esta forma, te puedes olvidar bastante del tema, porque por un lado solo tienes que inyectar emisiones de eventos, y por el otro lado aclarar las reglas lógicas en 1 script.

Insisto, tener todas las condiciones de la lógica de seguridad centralizadas, con una sintaxis específica que permite clarificar y legibilizar todo. Y creo que se ha conseguido, JavaScript puede leerse bien, pero es mucho mejor tenerlo separado, con una lógica aparte, en un lenguaje aparte. Esto, calculo, hará que puedas escalar más fácilmente programas que necesitan incorporar seguridad.

Y ya está, es todo.