@okzgn/estructura-js
v1.19.0
Published
A lightweight dependency-free JavaScript framework that lets you assign functions to be automatically attached to custom or extended data types, based on one or multiple arguments.
Maintainers
Readme
Estructura
Estructura es un framework JavaScript ligero y sin dependencias, que te permite asignar funciones que se vincularán automáticamente a tipos de datos que puedes crear o extender y que pueden ser de uno o múltiples argumentos.
Ejemplo de Caso de Uso
Imagina tener que manejar diferentes tipos de datos con lógica específica para cada combinación:
// ❌ El problema: código repetitivo y difícil de mantener
function processData(data, format, destination){
if(typeof data === 'string' && format === 'json' && destination.type === 'api'){
return sendJSONToAPI(data, destination);
}
else if(Array.isArray(data) && format === 'csv' && destination.type === 'file'){
return saveArrayAsCSV(data, destination);
}
else if(typeof data === 'object' && format === 'xml' && destination.type === 'database'){
return insertXMLToDB(data, destination);
}
// ... decenas de combinaciones más
}
// ✅ La solución con Estructura: limpio, extensible y mantenible
_e.fn({
String: {
JSON: {
API: { process: (args) => sendJSONToAPI(...args) }
}
},
Array: {
CSV: {
File: { process: (args) => saveArrayAsCSV(...args) }
}
},
Object: {
XML: {
Database: { process: (args) => insertXMLToDB(...args) }
}
}
});
_e.subtype({
// Aquí defines: JSON, API, CSV, File, XML, y Database.
});
// Uso simple y elegante
_e(myData, format, destination).process();📚 Índice de Contenidos
1. Introducción
2. Instalación y Configuración
3. Guía de Inicio Rápido
4. Conceptos Previos
- Despacho de Funciones
- Despacho Simple vs. Múltiple
- Terminología de Estructura
- Por Qué es Útil el Despacho Múltiple
5. Documentación de la API
- Función Principal o Despachador
- Registrar Funciones para Tipos
- Registrar Nuevos Tipos
- Crear Instancias
- Identificar Tipos
6. Tipos y Subtipos
7. Conceptos Avanzados
8. Adicionales
- Preguntas Frecuentes
- Problemas Comunes
- Consideraciones
- Notas sobre Versiones Anteriores y Actual
- Licencia
Características Principales
- Sistema de Tipos Extensible: Define tus propios tipos (
.subtype()) para cualquier estructura de datos, yendo mucho más allá de los tipos principales de JavaScript. - Instancias Aisladas (
Sandboxing): Crea múltiples instancias de Estructura (.instance()) que no interfieren entre sí, cada una con su propio registro de tipos y funciones. - Robusto y Predecible: Las llamadas al
despachadorson deterministas. Para una misma configuración de tipos y funciones registradas (.fn()) para esos tipos, una llamada a_e(arg1, arg2, ...)siempre seguirá una misma secuencia de ejecución y devolverá el mismo conjunto demétodos. - Ligero y sin Dependencias: Menos de 30 KB sin comprimir (
ESMyUMD), ideal para el navegador o Node.js.
¿Cuándo Usar Estructura?
Estructura es ideal cuando necesitas manejar lógica compleja que depende de la naturaleza de tus datos, como en:
APIsPolimórficas: Crea funciones que se resuelven automáticamente, comoendpoint(url_string, different_data_inputs),render(html_element, content_variations).- Sistemas de Plugins: Permite que extensiones de terceros registren funciones sin modificar el núcleo de tu aplicación.
- Procesamiento de Datos: Escribe flujos de datos limpios que se adaptan a diferentes formatos de entrada (JSON, CSV, XML, etc.).
- Refactorización de Código Complejo: Reemplaza largas cadenas de
if/elseoswitchde comprobaciones con una solución más declarativa y mantenible.
Instalación
npm
Puedes instalar Estructura a través de npm:
npm install estructura-jsCDN (Navegador)
Puedes usarlo directamente en el navegador a través de un CDN:
<script src="https://unpkg.com/estructura-js"></script>Compatibilidad
Estructura está diseñado para ser casi universalmente compatible, funcionando sin problemas en una amplia gama de entornos de JavaScript, desde navegadores modernos y antiguos hasta Node.js, etc.
Resumen rápido:
Estructura soporta ESM, UMD y AMD en navegadores, Node.js y RequireJS.
| Formato | Uso en Node.js | Navegador moderno | Navegador antiguo |
|---------|------------------------|-------------------------|-------------------|
| ESM | import _e from ... | <script type="module">| ❌ Necesita transpilar |
| UMD | require(...) | <script src=...> | ✅ Soportado |
| AMD | define([...]) | Con RequireJS | ✅ Soportado |
Decisión rápida:
- Si tu entorno soporta módulos modernos → usa ESM.
- Si no → usa UMD o AMD según corresponda.
Formatos de Módulo
El paquete se distribuye en varios formatos para asegurar una integración sencilla, en general, con los siguientes sistemas de módulos:
Módulos ES (
ESM): El formato principal y moderno. Ideal para usar conimporten Node.js (o entornos similares) con herramientas de compilación como Vite, Rollup, Webpack, o en navegadores actualizados.- Node.js:
import _e from 'estructura-js';- Navegadores actuales:
<script type="module"> import _e from 'https://unpkg.com/estructura-js'; </script>UMD(Universal Module Definition): Proporciona máxima compatibilidad.CommonJS (Node.js): Funciona de forma nativa con
require().const _e = require('estructura-js');Navegadores (
Global): Si se incluye con un tag<script>normal, crea una variableglobal_e.<script src="https://unpkg.com/estructura-js"></script> <script> // La variable global '_e' se ha cargado correctamente const estructura = _e; // Ejemplo para mostrar los tipos de datos en la consola del navegador let tipos = _e.type('hola'); console.log(tipos); //> [ 'String', String: true ] </script>AMD (Asynchronous Module Definition): Compatible con cargadores de módulos como RequireJS. Véase Ejemplos.
Compatibilidad con Navegadores
El código de la distribución UMD no necesita transpilación, está escrito en sintaxis compatible con ES3/ES5, lo que garantiza su funcionamiento en todos los navegadores modernos y en la mayoría de los antiguos, incluyendo Internet Explorer 9+. La distribución ESM necesita transpilación para funcionar en ciertos navegadores.
Nota: Los ejemplos utilizan sintaxis de ES6 por brevedad, pero pueden ser convertidos fácilmente y adaptarse a entornos ES5 (como a funciones clásicas
function(){}) para ejecutarlos en navegadores antiguos.
Compatibilidad con Entornos
Estructura funciona perfectamente en cualquier entorno que soporte la sintaxis ES5 (Node.js, Deno, Bun, etc.).
Guía de Inicio Rápido
El concepto central es simple: asigna funciones para tipos o combinaciones de tipos y llama al despachador _e() con los datos correspondientes.
import _e from 'estructura-js';
// Asignar métodos para tipos específicos
_e.fn({
Array: {
log: (args) => console.log(`Un array: ${args[0]}`)
},
Number: {
log: (args) => console.log(`Un número: ${args[0]}`)
},
// Asigna un método para la secuencia (combinación) de tipos '_e(String, Number)'
String: {
Number: {
combine: (args) => console.log(`Combinado: ${args[0]} y ${args[1]}`)
}
}
});
// Llamar al despachador
_e(['hola']).log(); //> "Un array: hola"
_e(12345).log(); //> "Un número: 12345"
_e('texto', 67890).combine(); //> "Combinado: texto y 67890"Conceptos Previos
Antes de profundizar en Estructura, es importante entender algunos conceptos fundamentales que te ayudarán a aprovechar al máximo este framework.
Despacho de Funciones
¿Qué es el Despacho de Funciones?
El despacho de funciones es el mecanismo que determina qué función específica se ejecutará cuando hay múltiples opciones disponibles. Es como un "enrutador inteligente" que selecciona la función correcta basándose en ciertos criterios.
Ejemplo Simple:
// Sin despacho: código repetitivo
function processData(data){
if(Array.isArray(data)){
return data.map(item => item.toUpperCase());
}
else if(typeof data === 'string'){
return data.toUpperCase();
}
else if(typeof data === 'number'){
return data.toString().toUpperCase();
}
// ... más condiciones
}
// Con despacho: más elegante
_e.fn({
Array: { process: (args) => args[0].map(item => item.toUpperCase()) },
String: { process: (args) => args[0].toUpperCase() },
Number: { process: (args) => args[0].toString().toUpperCase() }
});
// Uso limpio
_e(['hola', 'mundo']).process(); //> ["HOLA", "MUNDO"]
_e('hola').process(); //> "HOLA"
_e(123).process(); //> "123"Despacho Simple vs. Múltiple
Despacho Simple
Es el que usa la Programación Orientada a Objetos tradicional. La función a ejecutar se determina únicamente por el primer argumento (basándose en el cual se llama el método).
// Programación tradicional: solo importa el tipo del primer argumento
class MyArray extends Array {
combine(anotherArray){ // Solo sabe que 'this' es MyArray
return this.concat(anotherArray);
}
}
const arr1 = new MyArray(1, 2);
const arr2 = [3, 4];
arr1.combine(arr2); // Solo considera el tipo de 'arr1'Despacho Múltiple
Estructura utiliza despacho múltiple, donde la función se selecciona considerando los tipos de TODOS los argumentos, no solo del primero.
// Con Estructura: considera TODOS los argumentos
_e.fn({
Array: {
Array: {
combine: (args) => args[0].concat(args[1]) // Array + Array
},
String: {
combine: (args) => args[0].join(args[1]) // Array + String
}
},
String: {
Array: {
combine: (args) => [args[0]].concat(args[1]) // String + Array
}
}
});
_e([1, 2], [3, 4]).combine(); //> [1, 2, 3, 4] (Array + Array)
_e([1, 2], '-').combine(); //> "1-2" (Array + String)
_e('hola', [1, 2]).combine(); //> ["hola", 1, 2] (String + Array)Terminología de Estructura
Despachador
La función principal _e() que analiza los argumentos y decide qué hacer con ellos.
// '_e()' es el despachador
const result = _e('datos', 123); // Analiza: String + NumberMétodos
Funciones que se adjuntan al objeto que devuelve el despachador. Se llaman después de la evaluación de tipos.
_e.fn({
String: {
length: (args) => args[0].length // ← Esto es un MÉTODO
}
});
_e('hola').length(); // Se llama DESPUÉS del despachoManejadores
Funciones que se ejecutan automáticamente cuando los tipos coinciden, antes de devolver el objeto con métodos.
_e.fn({
String: (str) => { // ← Esto es un MANEJADOR
console.log('Procesando:', str);
// Se ejecuta automáticamente
}
});
_e('hola'); //> "Procesando: hola" (se imprime inmediatamente)Tipos, subtipos y alias
En la documentación, los siguientes términos son tipos en sentido general, aunque tienen pequeñas diferencias entre sí:
Tipos: categorías principales de datos, integrados por defecto, de estos se derivan todos los subtipos.
Subtipos: categorías personalizadas de datos que se pueden crear a partir de los tipos principales, con las características que se especifiquen.
Alias: nombres adicionales que se asignan a tipos y subtipos para categorizar, agrupar o relacionar.
_e.subtype({
// Crear un subtipo, a partir del tipo Object
Object: (input) => input.name && input.email ? 'User' : false,
// Crear un alias, a partir del tipo Array
Array: 'Collection'
});
_e.fn({
// Registrar método para el subtipo
User: {
hello: (args) => `¡Hola ${args[0].name}!`
},
// Registrar método para el alias
Collection: {
combine: (args, char) => args[0].join(char)
}
});
// Usos
const person = { name: 'Ana', email: '[email protected]' };
_e(person).hello(); //> "¡Hola Ana!"
const list = ['1', '2', '3'];
_e(list).combine(','); //> "1,2,3"Por Qué es Útil el Despacho Múltiple
Problema Común: Funciones Polimórficas
Por ejemplo, una función save() que debe comportarse diferente según los tipos de datos:
// Enfoque tradicional: código espagueti
function save(data, to){
if(typeof data === 'string' && typeof to === 'string'){
// Guardar texto en archivo
}
else if(Array.isArray(data) && to instanceof Database){
// Guardar array en base de data
}
else if(typeof data === 'object' && to.type === 'API'){
// Enviar objeto a API
}
// ... otras combinaciones
}// Con Estructura: declarativo y extensible
_e.fn({
String: {
String: {
save: (args) => saveTextFile(args[0], args[1])
}
},
Array: {
Database: {
save: (args) => saveToDB(args[0], args[1])
}
},
Object: {
API: {
save: (args) => sendToAPI(args[0], args[1])
}
}
});
// Uso limpio
_e('contenido', 'archivo.txt').save();
_e([1,2,3], myDB).save();
_e({data: 'json'}, myAPI).save();Ventajas Clave
- Extensibilidad: Permite añadir nuevas combinaciones manteniendo separadas las responsabilidades del código.
- Legibilidad: La lógica está organizada por tipos, no mezclada en
if/else. - Mantenibilidad: Cada combinación de tipos tiene su propio espacio.
- Composabilidad: Diferentes módulos o instancias pueden registrar sus propias funciones.
Documentación de la API
_e(arg1, arg2, ...)
La función principal o despachador. Cada argumento representa un valor (input).
- Analiza los tipos de los argumentos proporcionados.
- Busca asignaciones para toda la secuencia (combinación) de tipos.
- Devuelve un nuevo objeto (
output) con losmétodoscorrespondientes adjuntos y/o ejecutamanejadores.
_e.fn(assignments)
Registra asignaciones de métodos y/o manejadores para tipos, subtipos, alias o combinaciones.
assignments: Puede ser un objeto o una función.
Objeto:
En el cual cada clave se refiere a un nombre de tipo y cada valor es un objeto anidado con
métodoso una función (manejador). Cada nivel de anidación se refiere al tipo de cada argumento en orden que se pasa aldespachador(función principal)._e.fn({ // Asigna métodos para '_e(Object)' Object: { keys: (args) => Object.keys(args[0]) }, // Los niveles de anidación se refieren a '_e(Array, String)' y sus métodos Array: { String: { join: (args) => args[0].join(args[1]) } } }); const keys = _e({ a: 1, b: 2 }).keys(); console.log(keys); //> ["a", "b"] const join = _e(['1', '2', '3'], ',').join(); console.log(join); //> "1,2,3"Los
métodosque registres recibirán los argumentos deldespachadorcomo unarrayen el primer argumento. (Véase "Cómo se Pasan los Argumentos delDespachador")._e.fn({ String: { repeat: (args, times) => args[0].repeat(times) } }); const repeated = _e('hola ').repeat(3); console.log(repeated); //> "hola hola hola "Métodos GlobalesTambién puedes asignar
métodosque estarán disponibles sin importar el tipo de los argumentos (Any) de una instancia registrándolos en el primer nivel del objeto de asignaciones (elmétodono estará disponible y se generará una colisión si ya existe un método con el mismo nombre)._e.fn({ // Este método estará disponible para todas las llamadas a '_e()' timestamp: () => `Procesado a las: ${Date.now()}` }); console.log(_e(123).timestamp()); //> "Procesado a las: 1700000000000" console.log(_e('abc').timestamp()); //> "Procesado a las: 1700000000001"Colisión de
MétodosoManejadores⚠️ IMPORTANTE: Modo del Framework, En Colisiones Lo General Sobrescribe a lo Específico
Cuando un valor (
input) corresponde a múltiples tipos, todos susmétodosse fusionan en el objeto resultante. Si dos o más tipos asignan unmétodoomanejadorcon el mismo nombre, se producirá una colisión. En este caso, elmétodoomanejadordel tipo más general (menos específico, principal) prevalecerá, sobrescribiendo al de subtipo o alias más específico. Puedes consultar el orden de especificidad de cualquier valor usando.type().Orden de sobrescritura en colisión de métodos:
- Si existe, se usa un
método/manejadorglobal. - Si no, se usa un
método/manejadorde tipo principal. - Si no, se usa un
método/manejadorde tipo derivado. - Si no, usa el de subtipo/alias.
Ejemplo:
| Nivel de especificidad | Ejemplo | Prioridad ↑ | |-----------------------------|---------------------------|-------------| | Subtipo o Alias |
AnotherCollection| 1 | | Tipo derivado |Array| 2 | | Tipo principal |Object| 3 | |Método/Manejadorglobal |myMethod| 4 |Métodosde subtipos o alias:
_e.fn({ AnotherCollection: { myMethod: ... }})Métodosde tipos derivados:
_e.fn({ Array: { myMethod: ... }})Métodosde tipos principales:
_e.fn({ Object: { myMethod: ... }})_e.fn({ myMethod: ... })En el ejemplo anterior, significa que
myMethodparaObjectsobrescribirá al deAnotherCollection.Otro ejemplo:
// Registramos métodos con el mismo nombre para 'Array' y 'Object'. _e.fn({ // Método para el tipo derivado: Array Array: { logType: () => console.log('Tipo Derivado: Array') }, // Método para el tipo general (principal): Object Object: { logType: () => console.log('Tipo General (Principal): Object') } }); // Un array es tanto de tipo 'Array' como de tipo 'Object'. // Al haber una colisión, se prevalecerá el método del tipo más general (principal 'Object'). _e([1, 2, 3]).logType(); //> "Tipo General (Principal): Object"Nota: Esta decisión de diseño garantiza que los
métodosomanejadores globalespuedan actuar como un'fallback'predecible y consistente, asegurando que una función siempre esté disponible si no se encuentra una más específica. O para prevenir quemétodosomanejadoresregistrados más recientes sobrescriban a los anteriores.- Si existe, se usa un
Función:
Se puede asignar para que se ejecute en cualquier llamada a una instancia, se llama
manejadorraíz._e.fn((arg1, arg2, /*...,*/ argN) => { // Código... });Véase "Conceptos Avanzados:
Manejadores" o "Secuencia de Ejecución por Especificidad".
Fusión de Registros de Asignaciones (Registro Aditivo)
Si llamas a .fn() varias veces para registrar asignaciones de manejadores o métodos para los mismos tipos, las asignaciones se fusionan en lugar de sobrescribirse. Este comportamiento aditivo sirve para sistemas de plugins o para organizar el código en módulos, ya que permite añadir nueva funcionalidad de forma segura.
Para
Manejadores:Hay una regla importante: una vez que se asigna un
manejadora un tipo, seguirá siendo unmanejador.Si a continuación registras un objeto de
métodospara ese mismo tipo, los nuevosmétodosse añadirán almanejador, pero no perderá su capacidad de auto-ejecutarse.// Asignamos un manejador para 'Number' _e.fn({ Number: (num) => console.log(`Manejador ejecutado para: ${num}`) }); // Fusionamos un objeto con un nuevo método _e.fn({ Number: { isEven: (args) => args[0] % 2 === 0 } }); // El manejador se auto-ejecuta y tiene el nuevo método disponible const myNumber = _e(10); //> Manejador ejecutado para: 10 console.log(myNumber.isEven()); //> trueVéase "Conceptos Avanzados:
Manejadores" y "Colisión deMétodosoManejadores".Para
Métodos:Los objetos con
métodospara los mismos tipos se fusionarán en lugar de sobrescribirse.// Registro inicial en el núcleo de la aplicación _e.fn({ String: { log: (args) => console.log(`[LOG]: ${args[0]}`) } }); // Más tarde, un 'plugin' añade nueva funcionalidad al tipo String _e.fn({ String: { wordCount: (args) => args[0].split(' ').length } }); // Ambos métodos están ahora disponibles gracias a la fusión const myText = _e('Estructura es muy flexible'); myText.log(); //> [LOG]: Estructura es muy flexible console.log(myText.wordCount()); //> 4
Cómo se Pasan los Argumentos del Despachador
⚠️ IMPORTANTE: Distinción Crucial
Hay una diferencia clave en cómo los métodos o manejadores reciben los argumentos del despachador.
Métodos: reciben todos los argumentos en un array como primer parámetro.Manejadores: reciben los argumentos directamente.
| Tipo | Argumentos de función | Ejemplo de acceso |
|-------------|-------------------------------|-------------------|
| Método | (args, extra...) | args[0] |
| Manejador | (arg1, arg2, ...) | arg1 |
Ejemplos:
Métodos:_e.fn({ String: { // 'args' es ['Hola'] myMethod: (args) => console.log(`El primer argumento es: ${args[0]}`) } }); _e('Hola').myMethod(); //> "El primer argumento es: Hola"Manejadores:_e.fn({ // 'arg1' es 'Hola' String: (arg1) => { console.log(`Recibí directamente: ${arg1}`); } }); _e('Hola'); //> "Recibí directamente: Hola"
_e.subtype(definitions)
Registra definiciones de tipos nuevos para cada instancia de Estructura. Se llaman subtipos y alias.
definitions: Un objeto donde cada clave es un nombre de tipo predefinido y cada valor puede ser:
Una cadena de texto (
string) para crear un alias simple.Un
arrayde cadenas de texto para asignar múltiples alias a la vez.Una función evaluadora que recibe el valor (
input) y decide el tipo/subtipo deinput:Argumentos disponibles en la función:
input: El valor que se está analizando.primitive_type: El nombre del tipo principal o derivado del que proviene (ej.'Object','String').getTypes: Una función que, al ejecutarsegetTypes(), devuelve una copia segura del array/mapa con todos los tipos detectados hasta ese momento en la cadena de recursividad. (Al ser una función, garantiza un rendimiento óptimo al no clonar el mapa a menos que lo solicites explícitamente).
Valores que puede retornar la función:
- Una cadena de texto con el nombre nuevo del subtipo.
- Un booleano
truesi el nombre de la clave debe usarse como el nombre del subtipo. - Si no coincide con el subtipo:
undefined,null,false, o''.
// Crear subtipos
_e.subtype({
// Con una función
Object: (input) => (input.userId ? 'User' : false),
// Con un string (un alias simple)
RegExp: 'RegexPattern',
// Con un array (múltiples alias)
// Un 'Array' ahora también es de tipo 'Collection' y 'OrderedList'
Array: ['Collection', 'OrderedList']
});
// Registrar funciones para los nuevos tipos
_e.fn({
User: {
hello: (args) => console.log(`Hello, ${args[0].name}!`)
},
RegexPattern: {
test: (args, str) => args[0].test(str)
},
// Se pueden registrar métodos para CUALQUIER alias
Collection: {
isCollection: () => true
},
OrderedList: {
count: (args) => args[0].length
}
});
const user = { userId: 'u-123', name: 'Alex' };
_e(user).hello(); //> "Hello, Alex!"
const hasNumber = _e(/\d+/).test('abc-123');
console.log(hasNumber); //> true
// Ahora los métodos de ambos alias están disponibles
const myList = _e([1, 2, 3]);
console.log(myList.count()); //> 3
console.log(myList.isCollection()); //> trueAdemás, definitions puede ser una cadena de texto como 'browser-dom', que sirve para cargar subtipos predefinidos para el DOM de navegadores en instancias.
Tipos Predefinidos
Principales:
Null,Undefined,Boolean,String,Number,NaN,BigInt,Symbol,Function,Object.Derivados:
De
Object:Array,RegExp,Date, etc. propios de la especificación básica de JavaScript.O dependiendo del entorno, especificación JavaScript, o navegador:
Map,Set,Promise, etc.
Subtipos Predefinidos: 'browser-dom'
Este conjunto de subtipos simplifica drásticamente la manipulación del DOM al agrupar cientos de tipos de objetos específicos del navegador en unas pocas categorías potentes y genéricas.
Cómo Importar o Activar browser-dom
// Activar en la instancia por defecto
_e.subtype('browser-dom');
// O en una instancia nombrada
const domAPI = _e.instance('domAPI');
domAPI.subtype('browser-dom');
/*
* Ejemplos de uso en un navegador.
* Nota: El resultado de '.type()' se muestra aquí ordenado desde el
* tipo más específico al más general para mayor claridad, pero
* el orden que devuelve '.type()' por defecto es el inverso.
*/
console.log(domAPI.type(document.head)); //> [ "Node.HEAD", "Node", "HTMLHeadElement", "Object" ]
console.log(domAPI.type(document)); //> [ "Document", "Browser", "HTMLDocument", "Object" ]
console.log(domAPI.type(window)); //> [ "Browser", "Window", "Object" ]Subtipos Identificados
Subtipo Node
Representa nodos o elementos individuales en el DOM. Es la categoría más amplia y abarca:
- Todos los Elementos HTML: Desde
HTMLHtmlElementhastaHTMLDivElement,HTMLInputElement,HTMLTemplateElement, etc. (cualquier etiqueta que se pueda escribir).
Subtipo Dinámico:
Node.<TAG_NAME>Después de que un elemento es identificado como
Node, para más precisión, el framework crea un subtipo adicional usando su propiedadtagName.Ejemplos:
Nota: En los siguientes ejemplos, por claridad, los tipos se muestran ordenados desde el más específico al más general, pero el orden que devuelve
.type()es el inverso.
- Un elemento
<div>se clasifica como[ "Node.DIV", "Node", "HTMLDivElement", "Object" ].- Un elemento
<button>se clasifica como[ "Node.BUTTON", "Node", "HTMLButtonElement", "Object" ].
- Elementos SVG y MathML: Como
SVGSVGElementyMathMLMathElement. - Elementos desconocidos u obsoletos: Como
HTMLUnknownElementyHTMLMarqueeElement. - Nodos que no son elementos: Nodos de texto (
Text), comentarios (Comment), fragmentos de documento (DocumentFragment) y atributos (Attr).
Subtipo Nodes
Representa colecciones o listas de nodos, que son el resultado de consultas al DOM.
NodeList(devuelto pordocument.querySelectorAll()).HTMLCollection(devuelto pordocument.getElementsByTagName()oelement.children).HTMLAllCollection(una colección obsoleta).
Subtipo Document
Identifica específicamente el objeto document principal de la página, para normalizar cuando el tipo del objeto es HTMLDocument (en ciertos navegadores).
Subtipo Browser
Identifica objetos globales del entorno del navegador que no son parte del contenido del DOM.
Window(el objetoglobalwindow).Navigator(el objetonavigatorcon información del navegador).Screen(el objetoscreencon información de la pantalla).Location(el objetolocationcon información de la URL).History(el objetohistorypara la navegación).
_e.instance(name)
Crea o recupera una instancia aislada de Estructura. Esto es ideal para evitar colisiones en aplicaciones grandes o al crear librerías.
const myAPI = _e.instance('myAPI');
const anotherAPI = _e.instance('anotherAPI');
// Las asignaciones en 'myAPI' no afectan a 'anotherAPI'
myAPI.fn({ String: { log: () => console.log('Log de myAPI') } });
anotherAPI.fn({ String: { log: () => console.log('Log de anotherAPI') } });
myAPI('test').log(); //> "Log de myAPI"
anotherAPI('test').log(); //> "Log de anotherAPI"_e.type(input)
Una herramienta de utilidad que te permite saber los tipos de cualquier variable en orden de especificidad (desde el más general en el índice 0, hasta el más específico en el último índice). Devuelve un array que también funciona como mapa de todos los tipos detectados (las propiedades devueltas con el nombre del tipo y valor true sirven para verificaciones rápidas).
const types = _e.type({ id: 1 });
console.log(types); //> [ 'Object', Object: true ]
if(types['Object']){
console.log('Verificación rápida, es un objeto.');
}Conceptos Avanzados: Manejadores
Estas funciones se comportan de manera diferente, ofrecen más flexibilidad y pueden:
Auto-ejecutarse:
Si la secuencia (combinación) de tipos coincide con la función (
manejador), se ejecutará automáticamente antes de que eldespachadordevuelva el objeto con losmétodos(que está como referencia enthis[si es función clásicafunction(){}]).Los argumentos del
despachadorse pasan directamente a la función (véase "Cómo se Pasan los Argumentos delDespachador").Contener más
manejadoresométodos:Al ser una función, puede tener propiedades que actúen como
sub-manejadoresométodospara un despacho más profundo.
Auto-ejecución de Manejadores
Puedes registrar una función que se ejecute si los tipos de los argumentos coinciden.
// Creamos una función que se ejecutará para cualquier 'String'
// Nota: La función recibe los argumentos del despachador directamente
const logString = (str) => {
console.log(`[LOG]: La cadena '${str}' fue procesada.`);
};
// Asignamos la función directamente bajo el tipo 'String'
_e.fn({
String: logString
});
// Al llamar a '_e()' con una string, la función se ejecuta automáticamente
_e('Mi primer evento'); //> "[LOG]: La cadena 'Mi primer evento' fue procesada."
_e('Otro evento más'); //> "[LOG]: La cadena 'Otro evento más' fue procesada."Secuencia de Ejecución por Especificidad
Permite crear middleware o capas de lógica que se construyen unas sobre otras.
Cuando un valor (input) pertenece a múltiples tipos (como un Array, que también es un Object), se ejecutarán en secuencia todos los manejadores que coincidan, desde el más específico hasta el más general.
Resumen rápido: Los manejadores se ejecutan del más específico al más general.
Ejemplo de secuencia:
| Especificidad | Ejemplo de tipo detectado |
|---------------|---------------------------------------|
| 1 (más alto) | Array |
| 2 | Object |
| 3 (más bajo) | Manejador raíz (_e.fn(() => {...})) |
// Manejador para Arrays (muy específico)
_e.fn({
Array: (arr) => console.log(`[LOG]: Manejador para Array ejecutado para: ${arr}`)
});
// Manejador para Objects (menos específico, ya que Array es un Object)
_e.fn({
Object: () => console.log('[LOG]: Manejador para Object ejecutado.')
});
// Manejador raíz (el más general)
_e.fn(() => {
console.log('[LOG]: Manejador raíz ejecutado.');
});
// Al llamar con un array, se ejecutan los tres manejadores en orden de especificidad
_e(['a', 'b']);
//> "[LOG]: Manejador para Array ejecutado para: a,b"
//> "[LOG]: Manejador para Object ejecutado."
//> "[LOG]: Manejador raíz ejecutado."Sobrescribir Métodos Dinámicamente
Si un manejador devuelve un objeto o función, los sub-manejadores o métodos que contenga sobrescribirán el conjunto de métodos que el despachador está agregando al objeto principal que devolverá (que está como referencia en this [si es función clásica function(){}]).
Esto permite crear APIs dinámicas donde el resultado de una función puede cambiar los métodos disponibles.
Ejemplo: Un validador que devuelve métodos diferentes según el resultado.
// Subtipo para identificar emails
_e.subtype({
String: (input) => (input.includes('@') ? 'Email' : false)
});
// Manejador para el tipo 'Email'
// Recibe el email como primer argumento, no envuelto en un array
const validateEmail = (email) => {
console.log(`Validando: ${email}`);
if(email.endsWith('@gmail.com')){
// Si es un email de Gmail, devuelve métodos específicos
return {
send: () => console.log('Enviando con la API de Gmail...'),
addToContacts: () => console.log('Añadiendo a contactos de Google.')
};
}
else {
// Para otros emails, devuelve un método genérico
return {
send: () => console.log('Enviando con SMTP genérico...')
};
}
};
// Registramos el manejador
_e.fn({
Email: validateEmail
});
// CASO 1: Email de Gmail
const gmailUser = _e('[email protected]');
//> "Validando: [email protected]"
gmailUser.send(); //> "Enviando con la API de Gmail..."
gmailUser.addToContacts(); //> "Añadiendo a contactos de Google."
// CASO 2: Otro email
const otherUser = _e('[email protected]');
//> "Validando: [email protected]"
otherUser.send(); //> "Enviando con SMTP genérico..."
// otherUser.addToContacts(); // Esto causa error, porque el método no fue devueltoFAQ (Preguntas Frecuentes)
¿Puedo usar Estructura con TypeScript?
No está diseñado para usarse con TypeScript, Estructura es una alternativa a la verbosidad de TypeScript.
¿Tiene alguna manera integrada para depurar?
Sí, actualmente se emiten advertencias y errores en consola (o se lanza un error en entornos sin consola) en estos casos:
- Si registras un subtipo con una función con error o si devuelve un tipo incorrecto.
- Si tratas de usar nombres no permitidos para instancias o
métodos, como: nombres de propiedades heredadas deObject.prototype, palabras reservadas de JavaScript, y nombres de propiedades que usa el framework. - Si la instancia que tratas de crear ya existe.
- Si hubo un conflicto con nombres de
métodos(colisiones). - Si un
manejadorpresenta errores al ejecutarse. - Si tratas de registrar un subtipo o
métodode manera incorrecta.
¿Cómo depurar
métodosno encontrados?Véase "Problemas comunes".
¿Se puede incluir llamadas a Estructura en bucles muy repetitivos?
Véase "Consideraciones".
Problemas comunes
Manejadorno se ejecutaCompruebe que fue asignado a los tipos de datos o argumentos adecuados verificando con
.type()los tipos detectados. Y revise "Secuencia de Ejecución por Especificidad" o "Conceptos Avanzados:Manejadores".Error de
métodosno encontradosSi al invocar un
métodoaparece un error, asegúrese de que elmétodoesté registrado para los tipos de datos o argumentos correctos, y que los datos sean exactamente de esos tipos, se puede usar.type()para verificar los tipos detectados de los datos.Métodosse sobrescribenRevise cómo funciona el orden de sobrescritura en caso de
métodoscon el mismo nombre en "Colisión deMétodosoManejadores" y la sección de sobrescritura dinámica "SobrescribirMétodosDinámicamente".
Consideraciones
Rendimiento
En la v1.19.0:
Se optimizó el registro de subtipos anidados en
.subtype(), reduciendo a la mitad el tiempo de resolución en anidaciones profundas.Hasta la v1.18.0:
En dispositivos de gama baja se experimentan retrasos (
lags) en bucles de alta frecuencia. Por ejemplo:Lagde aprox. 1 segundo por cada 100000 ejecuciones seguidas en:- Dispositivo: "HP 15 Notebook PC".
- Procesador: "AMD A8-7410 APU with AMD Radeon R5 Graphics, 2200 Mhz, 4 Core(s), 4 Logical Processor(s)".
- S.O.: "Windows 10 Home" de 64 bits.
- Procesos simultáneos: procesos comunes del S.O. ejecutándose.
- Entorno de ejecución: "Google Chrome 138.0".
- Contexto de ejecución: limpio, 10 tipos de datos variados definidos con 100
métodossimples en total asignados equitativamente. - Resumen de ejecución: bucle
forde 10000 repeticiones con 10 llamadas aldespachadorpara los tipos definidos.
Inconsistencias
Como se menciona en la documentación del código de
jQuery(en los comentarios de la funciónisFunction, línea 77), en ciertas versiones antiguas de navegadores hay nodos del DOM que podrían retornar como tipo primitivo'Function'en lugar de'Object'. Por eso se ha integrado la siguiente solución de compatibilidad con los subtipos predefinidosbrowser-dom:_e.subtype({ 'Function': function(input){ return typeof input.nodeType === "number" || typeof input.item === "function" ? 'Object' : null; } });
Notas sobre Versiones Anteriores y Actual
Hasta la v1.19.0:
Bugfixcrítico: se resolvió una fuga de memoria (Memory Leak) en el sistema interno de cola de mensajes de consola (setInterval).- Rendimiento: se optimizó el registro de subtipos anidados (
trash codeeliminado en.subtype()), reduciendo a la mitad el tiempo de resolución en anidaciones profundas. - Se mejoraron o actualizaron los comentarios JSDoc del código y documentación.
- En general, la funcionalidad se ha mantenido estable.
En la v1.18.0:
- Se han eliminado redundancias y se ha corregido el flujo de mensajes de consola.
En la v1.17.0:
- Se ha mejorado la seguridad en
subtype_definition_executiony muestra de advertencias y errores en consola. - Se corregieron inconsistencias en
subtype,is_correct_object_property_name,merge_type_fns_nodey mensajes de consola. - Compatibilidad integrada para inconsistencias de ciertos navegadores con subtipos predefinidos
browser-dom. - Se ha degradado la versión de Jest para pruebas unitarias a v29.7+ para asegurar la compatibilidad con versiones anteriores de Node.js (v14.15+).
- Se mejoraron y agregaron pruebas unitarias para cobertura extra.
- Las actualizaciones se centraron principalmente en la documentación.
- Se ha mejorado la seguridad en
En la v1.15.0:
- Se agregaron pruebas unitarias con Jest.
- En los subtipos predefinidos
browser-domse eliminó el aliasBrowserpara el subtipoDocument(no breaking change). - Se actualizó la documentación
JSDocdel código para reflejar las referencias a "manejadores".
En la v1.14.0:
En la documentación, los "
manejadores" reemplazaron a los "nodo/s híbrido/s".En la v1.9.0:
Se actualizó la documentación
JSDocdel código para reflejar cambios de versiones anteriores.En la v1.8.0:
Se mejoró: el adjuntador de
métodos(attach_resolved_methods), y el aislamiento de instancias en los registradores de funciones (fn) y subtipos (subtype) para entornos JavaScript como Node.js.Anteriores a la v1.6.0:
El módulo en formato
ESMno estaba configurado correctamente, lo que podía causar problemas de importación.
Licencia
MIT © Desarrollado por OKZGN
