Skip to content

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).searchParamsQuieres 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 = strEstá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…UsaComportamiento
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 cursosingleFlight(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 veznew QuestQueue({ simultaneous: n })
Desistir de una promesa que tarda demasiadowithTimeout(promise, ms)
…y seguir con un valor por defecto en vez de lanzarwithTimeoutFallback(promise, ms, fallback)
Una promesa que resuelves desde otro sitio por completodeferred()
Encadenar pasos asíncronos al estilo de Koa, cada uno capaz de envolver al siguientecompose(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ñoUsa
Una cadena pequeña que sobreviva a las recargaslocalStorageSetItem / localStorageGetItem / localStorageRemoveItem
Datos con estructura, muchos registros, o más de unos pocos MBnew WebDB({ dbName, stores }): una envoltura de promesas sobre IndexedDB
Un valor que pasa de esta página a la siguientecreateHandoff({ 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 respuestanew WorkerClient({ create }): casa cada respuesta con su petición por identificador
Dos extremos cualesquiera de MessagePortcreatePortBridge(port)
Dos ventanas o iframes que tienen que encontrarseacceptPortBridge() 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…UsaNota
Una copia que nadie más compartacloneDeep(value)Se apaña con las referencias circulares y con los tipos nativos habituales.
Saber si dos valores son igualesisEqual(a, b)Comparación en profundidad, no identidad de referencia.
Juntar dos objetosmerge(a, b)Fusión superficial: ganan las claves de b, y a queda modificado.
Quitar algunas clavesfilterObj(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.
  • segmentByRanges y paginateText sirven 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:

  1. ranuts/stream: interpreta el SSE y luego pliega los incrementos en una instantánea con createStreamAccumulator(). 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.
  2. ranuts/conversation: proyecta un registro de eventos de solo añadidura en nodos dibujables con createConversationEngine(). Decide qué es cada fila; no dibuja nada.
  3. <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ónContieneDónde corre
ranutsBarril raíz: las utilidades más la superficie visualnavegador + node
ranuts/utilsDOM/BOM, cadenas, objetos, números, color, tiempo, almacenamiento, …navegador + node*
ranuts/nodeServidor HTTP, enrutador, WebSocket, fs, flujos, middlewaresolo node
ranuts/visualEl motor de dibujo 2D (Canvas / WebGL / WebGPU)solo navegador
ranuts/i18nEl motor de traducción, sin DOMnavegador + node
ranuts/swEstrategias de caché y la mitad del protocolo de precarga que vive en el workerservice worker
ranuts/vnodeDOM virtual al estilo de Snabbdomnavegador
ranuts/streamLectura de SSE, plegado del flujo del modelo, presupuesto de tokensnavegador + node
ranuts/conversationDel registro de eventos a nodos de conversación dibujablesnavegador + 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.

Publicado bajo la licencia MIT.