solid-primitives: la colección oficial
solid-primitives es el monorepo oficial de utilidades reactivas de la comunidad Solid: decenas de paquetes diminutos, tree-shakeables y probados, cada uno con un sistema de etapas de madurez y un contrato de diseno estricto. La convencion create frente a make que distingue lo reactivo con dueno de lo imperativo con baja, por que son seguros en SSR, ejemplos como createMediaQuery createEventListener debounce y makePersisted, y el criterio de ingenieria para decidir cuando reutilizar la coleccion y cuando fabricar tu propio primitivo.
Qué es y cómo está organizada
solid-primitives no es una librería monolítica sino una familia de paquetes independientes, cada uno bajo el ámbito @solid-primitives/, cada uno resolviendo un problema acotado y publicándose por separado. Instalas solo el que necesitas y, como todo es tree-shakeable, tu bundle carga únicamente lo que usas. La lista es larga y crece; estos son los pilares que aparecen en casi cualquier app:
import { createMediaQuery } from "@solid-primitives/media";import { createEventListener } from "@solid-primitives/event-listener";import { debounce, throttle } from "@solid-primitives/scheduled";import { makePersisted } from "@solid-primitives/storage";const oscuro = createMediaQuery("(prefers-color-scheme: dark)");const buscar = debounce((q: string) => lanzar(q), 300);Cada paquete lleva además una etapa de madurez del 0 al 4 que anuncia su estabilidad: un primitivo en etapa 3 o 4 tiene API estable, pruebas y documentación; uno en etapa 0 o 1 es experimental. Esa señal te deja calibrar cuánto apoyarte en cada pieza según el riesgo que toleres, y es una de las razones por las que la colección se considera un lugar fiable del que copiar patrones aunque no la uses.
La superficie es amplia y cubre casi cualquier necesidad recurrente. Más allá de los pilares, encuentras @solid-primitives/resize-observer e @solid-primitives/intersection-observer para observar geometría y visibilidad, @solid-primitives/mouse y @solid-primitives/keyboard para entrada del usuario, @solid-primitives/timer para temporizadores con ciclo de vida, @solid-primitives/i18n para internacionalización reactiva y @solid-primitives/refs para trabajar con referencias y children. No hace falta memorizar la lista: basta recordar que, ante una tarea reactiva común, lo probable es que ya exista un paquete afinado esperándote, y que buscarlo antes de escribir cuesta menos que depurar tu versión casera.
create frente a make: la convención que lo explica todo
La colección codifica en sus nombres la distinción que ya rozaste al fabricar tus primitivos. Un primitivo create* es reactivo y con dueño: devuelve accessores o signals, rastrea sus argumentos y registra su limpieza con onCleanup, así que debe llamarse dentro de un scope reactivo. Un helper make* es imperativo y sin rastreo: hace una sola cosa, no observa argumentos reactivos y te devuelve una función de baja para que tú decidas cuándo cortar.
import { createEventListener, makeEventListener } from "@solid-primitives/event-listener";// create*: reactivo, se limpia solo con el duenocreateEventListener(window, "resize", () => actualizar());// make*: imperativo, te devuelve la baja para que la gestiones tuconst baja = makeEventListener(window, "scroll", () => medir());// ... cuando quieras: baja();La regla mental es directa: si necesitas que el primitivo reaccione a entradas cambiantes y se limpie con el componente, quieres un create*; si solo quieres enganchar algo una vez y llevarte el control de la baja, quieres un make*. Muchos paquetes ofrecen las dos variantes de la misma utilidad, y elegir bien entre ellas evita tanto las fugas como el rastreo innecesario.
SSR, tree-shaking y el criterio para decidir
Dos garantías hacen que la colección sea segura donde tus primitivos caseros tropiezan. La primera es el SSR: cada paquete protege sus accesos a APIs del navegador con guardas de servidor, así que un createMediaQuery en SolidStart no revienta al renderizar en el servidor —devuelve un valor por defecto sensato y despierta al hidratar—. La segunda es el tree-shaking real: al ser paquetes atómicos, importar debounce no arrastra el resto de la colección. Estas dos propiedades, más las pruebas y el mantenimiento de la comunidad, son justo las que cuesta sostener a mano y las que casi siempre inclinan la balanza hacia reutilizar.
El criterio de cuándo usarla frente a fabricar el tuyo es de ingeniería pura. Reutiliza solid-primitives cuando la necesidad es común y genérica —media queries, listeners, antirebote, persistencia, observadores—: ahí la colección ya resolvió las esquinas que tú descubrirías a golpes, incluido makePersisted, que es el createStoredSignal de producción con SSR e interoperabilidad ya resueltos. Fabrica tu propio primitivo, con el molde de la lección anterior, cuando la fuente es propia de tu dominio —un SDK interno, un protocolo de tu empresa, una política que ningún paquete cubre— o cuando estás aprendiendo y quieres ver el mecanismo desnudo. La consigna madura: reutiliza la mercancía común, construye solo aquello que te diferencia.
Varios primitivos en un mismo componente
El poder de la colección se ve cuando varios primitivos conviven en un componente sin que ninguno te obligue a gestionar su limpieza. Cada uno se ata al dueño y se retira solo al desmontar; tú te quedas con un componente declarativo donde cada línea dice qué observas, no cómo te suscribes ni cuándo te das de baja.
import { createSignal } from "solid-js";import { createMediaQuery } from "@solid-primitives/media";import { createEventListener } from "@solid-primitives/event-listener";import { makePersisted } from "@solid-primitives/storage";import { debounce } from "@solid-primitives/scheduled";function Editor() { const compacto = createMediaQuery("(max-width: 640px)"); const [borrador, setBorrador] = makePersisted(createSignal(""), { name: "nota" }); const guardar = debounce((t: string) => enviarAlServidor(t), 500); createEventListener(window, "keydown", (e) => { if (e.key === "Escape") setBorrador(""); // se limpia solo al desmontar }); return ( <textarea classList={{ compacto: compacto() }} value={borrador()} onInput={(e) => { setBorrador(e.currentTarget.value); guardar(e.currentTarget.value); }} /> );}Cuatro paquetes distintos colaboran —consulta de medios, persistencia, antirebote, escucha de teclado— y ninguno filtra un listener ni te pide un onCleanup explícito, porque cada uno lo lleva dentro. Ese es el dividendo de apoyarse en primitivos bien hechos: el ruido del ciclo de vida desaparece del componente y aflora la intención.