Elegir una utilidad
La referencia de la API enumera todas las exportaciones. Esta página responde a lo que aquella no puede: de dos cosas parecidas, cuál quiero y por qué.
Úsala cuando sepas más o menos lo que necesitas («que esto se ejecute menos», «solo una vez», «guardar esto», «hablar con un worker») pero no qué exportación lo hace.
Lo primero: ¿no lo trae ya la plataforma?
ranuts no pretende sustituir a la biblioteca estándar. Recurre primero a la plataforma y echa mano de una utilidad cuando esta aporte algo de verdad:
| En vez de… | La plataforma ya trae… | Usa la de ranuts cuando… |
|---|---|---|
cloneDeep(value) | structuredClone(value) | El valor lleva funciones o cualquier cosa que structuredClone rechace: aquella lanza un DataCloneError, mientras que cloneDeep copia lo que puede y conserva el resto por referencia. |
getAllQueryString(url) | new URL(url).searchParams | Quieres un objeto llano de una sola llamada en vez de un iterador. |
localStorageGetItem(key) | localStorage.getItem(key) | El código corre además donde no hay almacenamiento o está bloqueado: las envolturas devuelven '' en lugar de lanzar (modo privado de Safari, SSR, un iframe en zona de arena). |
escapeHtml(str) | textContent = str | Estás construyendo una cadena, no un nodo. |
Hacer algo con menos frecuencia
«que esto se llame menos» puede querer decir cuatro cosas distintas:
| Lo que quieres… | Usa | Comportamiento |
|---|---|---|
| Solo la última llamada de una ráfaga (un buscador, un redimensionado) | debounce(fn, ms) | Se ejecuta ms después de que la ráfaga pare. Durante la ráfaga no corre nada. |
| Un ritmo constante durante la ráfaga (la posición del desplazamiento, una lectura de progreso) | throttle(fn, ms) | La primera llamada corre al momento; después, como mucho una cada ms. |
| Que corra exactamente una vez en toda su vida (una inicialización, un aviso único) | once(fn) | La primera llamada evalúa; todas las siguientes devuelven ese mismo resultado. |
| Que quienes llamen a la vez compartan una sola petición en curso | singleFlight(fn) | La versión asíncrona de once: mientras una llamada está pendiente, las demás se suben a ella. |
memoize es el antiguo nombre de once y hace exactamente lo mismo: no guarda un resultado por argumento, que es lo que el nombre da a entender. En código nuevo, escribe once.
La diferencia que cuenta: con debounce en un manejador de teclas no se ejecuta nada mientras la persona escribe; con throttle algo se ejecuta todo el rato, solo que no en cada tecla. Una sugerencia de búsqueda quiere debounce; un contador de «caracteres restantes» quiere throttle.
Llevar el trabajo asíncrono con riendas
| Lo que quieres… | Usa |
|---|---|
| Correr muchas tareas, pero solo n a la vez | new QuestQueue({ simultaneous: n }) |
| Desistir de una promesa que tarda demasiado | withTimeout(promise, ms) |
| …y seguir con un valor por defecto en vez de lanzar | withTimeoutFallback(promise, ms, fallback) |
| Una promesa que resuelves desde otro sitio por completo | deferred() |
| Encadenar pasos asíncronos al estilo de Koa, cada uno capaz de envolver al siguiente | compose(middleware) |
Promise.all es lo acertado cuando quieres todas a la vez; QuestQueue lo es cuando «todas a la vez» abriría sesenta conexiones. withTimeout rechaza: acompáñalo de un catch, o usa la variante con valor de reserva cuando para ti agotar el tiempo no sea un error.
Guardar algo
| Duración y tamaño | Usa |
|---|---|
| Una cadena pequeña que sobreviva a las recargas | localStorageSetItem / localStorageGetItem / localStorageRemoveItem |
| Datos con estructura, muchos registros, o más de unos pocos MB | new WebDB({ dbName, stores }): una envoltura de promesas sobre IndexedDB |
| Un valor que pasa de esta página a la siguiente | createHandoff({ dbName, storeName, key }) |
Las envolturas localStorage* existen porque las llamadas nativas lanzan allí donde el almacenamiento no está disponible (el modo privado de Safari, un iframe en zona de arena, un navegador con los datos del sitio bloqueados), y caerse al leer es un fallo peor que quedarse sin una preferencia. Ellas devuelven '' y siguen adelante.
createHandoff es para el caso en que ninguna de las otras dos encaja: un valor que debe sobrevivir exactamente a una navegación y luego desaparecer.
Hablar entre contextos
| Entre… | Usa |
|---|---|
| Una página y un Web Worker, con petición y respuesta | new WorkerClient({ create }): casa cada respuesta con su petición por identificador |
Dos extremos cualesquiera de MessagePort | createPortBridge(port) |
| Dos ventanas o iframes que tienen que encontrarse | acceptPortBridge() de un lado y el apretón de manos del otro |
WorkerClient es lo que buscas cuando el worker responde preguntas: sin identificadores de petición, dos llamadas solapadas no pueden saber de quién es la respuesta que llegó. El puente es el nivel de abajo: úsalo cuando el tráfico no sea de petición y respuesta, o cuando el transporte ya exista.
Trabajar con objetos
| Lo que quieres… | Usa | Nota |
|---|---|---|
| Una copia que nadie más comparta | cloneDeep(value) | Se apaña con las referencias circulares y con los tipos nativos habituales. |
| Saber si dos valores son iguales | isEqual(a, b) | Comparación en profundidad, no identidad de referencia. |
| Juntar dos objetos | merge(a, b) | Fusión superficial: ganan las claves de b, y a queda modificado. |
| Quitar algunas claves | filterObj(obj, keys) | Devuelve una copia sin las claves indicadas. |
Idioma y texto
resolveLocale({ supported, … })decide cuál de tus idiomas usar, siguiendo la cadena de siempre (una elección explícita, el almacenamiento,navigator.languages, un valor de reserva). Responde a «qué idioma», no a «qué dice esta cadena».createI18n/useI18n(ranuts/i18n) es el motor de traducción: diccionarios planos de mensajes, interpolación con{param}y cambio en caliente.segmentByRangesypaginateTextsirven para maquetar texto: la primera, desplazamientos y resaltados; la segunda, cortar el texto en páginas que quepan en una caja.
Usa resolveLocale aunque no uses el motor de i18n: la decisión que toma —respetar el orden completo de navigator.languages del lector, no solo el primero— es justo la parte que se hace mal con facilidad.
Transmitir la respuesta de un modelo
Tres capas, y cada una sirve por sí sola:
ranuts/stream: interpreta el SSE y luego pliega los incrementos en una instantánea concreateStreamAccumulator(). Es neutral respecto al proveedor: los incrementos de texto, de razonamiento y de llamadas a herramientas acaban con la misma forma, los emita quien los emita.ranuts/conversation: proyecta un registro de eventos de solo añadidura en nodos dibujables concreateConversationEngine(). Decide qué es cada fila; no dibuja nada.<r-conversation>en ranui: el elemento que dibuja esos nodos, mantiene la vista pegada al fondo y concilia las filas.
Quédate en la capa 1 si solo pintas texto; añade la 2 cuando la transcripción tenga una estructura que merezca proyectarse; añade la 3 cuando quieras el desplazamiento y la conciliación ya resueltos.
De qué entrada importar
Cada subruta es un barril independiente del que se puede podar lo no usado. Importa de la que sea dueña del símbolo, nunca de una ruta interna del código.
| Importación | Contiene | Dónde corre |
|---|---|---|
ranuts | Barril raíz: las utilidades más la superficie visual | navegador + node |
ranuts/utils | DOM/BOM, cadenas, objetos, números, color, tiempo, almacenamiento, … | navegador + node* |
ranuts/node | Servidor HTTP, enrutador, WebSocket, fs, flujos, middleware | solo node |
ranuts/visual | El motor de dibujo 2D (Canvas / WebGL / WebGPU) | solo navegador |
ranuts/i18n | El motor de traducción, sin DOM | navegador + node |
ranuts/sw | Estrategias de caché y la mitad del protocolo de precarga que vive en el worker | service worker |
ranuts/vnode | DOM virtual al estilo de Snabbdom | navegador |
ranuts/stream | Lectura de SSE, plegado del flujo del modelo, presupuesto de tokens | navegador + node |
ranuts/conversation | Del registro de eventos a nodos de conversación dibujables | navegador + node |
* ranuts/utils es ancho: la mayor parte mira al navegador, pero las ayudas puras (cadenas, objetos, números, compose, cloneDeep, …) corren en cualquier sitio. No importes ranuts/node en código de navegador. Arrastra fs, http y child_process.
¿Sigues con dudas?
Busca en la referencia de la API: están todas las exportaciones con su firma y una línea de descripción, generadas a partir del código. Si después de leer ambas líneas dos de ellas siguen pareciendo intercambiables, eso es un fallo de documentación que vale la pena comunicar.