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 🙏

© 2026 – Pkg Stats / Ryan Hefner

dotrino-content

v0.3.8

Published

Nodo de contenido del ecosistema Dotrino: guarda los bytes del usuario direccionados por su hash (cid), en su propia maquina y, si el dueno quiere, respaldados en su propio bucket (R2, Backblaze, Hetzner, Storj o S3). Lo publico sale por URL directa; lo p

Downloads

1,775

Readme

dotrino-content

Nodo de contenido del ecosistema Dotrino: guarda y sirve media pesada (video/imagen/audio/archivos) direccionada por hash de contenido (cid), autohospedado por el usuario. Diseño completo en docs/DISENO.md; estado/continuación en docs/HANDOFF.md.

Estado: Fase 2 (aparato del vault) + vistas previas públicas. Al core local se le suma la identidad: el node se enrola a tu bóveda y se puede administrar desde tus apps por el proxy, sin abrir puertos. El HTTP de administración sigue escuchando solo en 127.0.0.1; con --public se abre, aparte, un puerto que sirve únicamente vistas previas para que un enlace compartido tenga tarjeta en las redes. El transporte P2P entre aparatos es lo que queda de la Fase 3.

Uso

# una vez: enlazar este node a tu bóveda (saca el código de `dotrino-vault pair`)
npx dotrino-content enroll <código>

npx dotrino-content start [--port 3777] [--dir ~/.dotrino-content] \
  [--max-gb 50] [--max-blob-mb 512] [--gc-min 60] [--no-agent]

# con vistas previas públicas (ver más abajo antes de encenderlo)
npx dotrino-content start --public --public-port 3778 --public-egress-gb 5 \
  --public-url https://content.tudominio.com

Al enrolar, el node genera su propia llave y muestra un código que tienes que tipear en la bóveda para aprobarlo: ese código no viaja por la red, así que aprobar exige tener delante esta máquina. La clave maestra nunca llega aquí — solo un certificado con caducidad, que el node renueva solo y que puedes revocar (dotrino-vault revoke <deviceId>) sin tocar el resto de tus aparatos.

Env: DOTRINO_CONTENT_DIR (datos), DOTRINO_CONTENT_LINK_DIR (enlace), PORT. Requiere Node ≥ 22.5. El core usa solo node:crypto y node:sqlite; la identidad viene de los pilares del ecosistema (@dotrino/remote-agent, @dotrino/identity).

Plano de control (Fase 2, por el proxy, cifrado)

Con el node enlazado, tus propias apps —cualquier aparato con un certificado de la misma bóveda— pueden administrarlo a distancia dentro de una sesión cifrada:

hello                    quién es el node (owner, versión, uso de disco, tope)
put {data,mime,…}        guardar algo pequeño desde otro aparato tuyo (≤ 256 KB)
get <cid>                leerlo de vuelta (≤ 256 KB)
list · stat <cid> · stats  qué guarda
pin <cid> · unpin <cid>  retener / soltar
remove <cid>             borrar
acl <cid> public|private abrir o cerrar un blob (público es opt-in explícito)
meta <cid> {…}           nombre/título/descripción para la tarjeta de la vista previa
thumb <cid> <thumbCid>   enlazar la miniatura (otro blob, público por su cuenta)
gc                       recolectar vencidos ahora

put y get tienen un tope duro de 256 KB, y eso NO es un límite a subir: es la frontera. El plano de control es el proxy del ecosistema —trama de 1 MB, cola de mensajes, no un almacén—, así que por aquí pasa lo que es un mensaje: un post (un eco pesa cientos de bytes) y una miniatura (decenas de KB). Por eso no hay subida por partes: trocear sería disimular la frontera y acabar usando la infraestructura del ecosistema como transporte. Los originales suben en local por HTTP y, entre aparatos, por P2P.

Vistas previas públicas (--public, apagado por defecto)

Para qué es: para que un enlace que compartes tenga TARJETA en X, LinkedIn, WhatsApp o Telegram. No es para servir tu contenido — eso se sigue abriendo en la app, con la referencia en el #fragment, que nunca llega a ningún servidor. Lo que sale por este puerto es la miniatura que tú marcaste pública, no el archivo.

GET|HEAD /c/<cid>   los bytes, si pasan TODOS los cerrojos de abajo
GET      /p/<cid>   permalink: tarjeta (og:*) + botón "Abrir" hacia la app
GET      /robots.txt · /health

Cinco cerrojos, y ninguno se puede saltar desde fuera:

| | | |---|---| | Solo lo público y en claro | lo cifrado no sale ni marcado público a mano en el índice | | Solo imágenes de mapa de bits | JPEG, PNG, GIF, WebP, AVIF. SVG no: es un documento que ejecuta scripts | | El tipo se comprueba en los bytes | el Content-Type lo declara quien sube, así que no se cree: un HTML subido como image/png responde 404 | | Tope de tamaño (--public-max-kb, 512) | es lo que hace que esto sea un servidor de miniaturas y no un CDN. 0 lo quita y entonces sirve originales: el ancho de banda lo pagas tú | | Límite por IP + techo diario | --public-rate (60/min) y --public-egress-gb, que se persiste y corta antes de mandar una respuesta que no quepa |

Lo privado responde 404, nunca 403: un 403 confirmaría que ese cid está aquí. Y robots.txt prohíbe todo (las tarjetas funcionan igual: los rastreadores de las redes piden la página cuando alguien pega el enlace, no indexan). --public-index lo levanta.

La miniatura la genera tu app al subir (con un canvas) y se sube como otro blob, que se enlaza con la op thumb. El node no decodifica imágenes: así no arrastra dependencias nativas. Enlazar una miniatura no la publica — se marca pública por su cuenta.

API HTTP (localhost)

POST   /c?ttl=<ms>&enc=1   subir (streaming; Content-Type = mime) → { cid, size, mime, existed }
GET    /c/<cid>            descargar/streamear (Range → 206; ETag = cid, immutable)
HEAD   /c/<cid>            size/mime/etag sin cuerpo
DELETE /c/<cid>            borrar
GET    /list               índice de blobs
POST   /pin/<cid>          retención (excluye del GC)     POST /unpin/<cid>
GET    /stats              nº de blobs, bytes usados, cuota
  • cid = sha256-<hex> (prefijo de algoritmo → extensible a BLAKE3 después; se usa SHA-256 de node:crypto porque no exige dependencias nativas y el .npmrc del ecosistema bloquea build scripts de npm).
  • Disco: blobs/<aa>/<bb>/<cid> (sharding); índice en SQLite (index.db).
  • Dedup por contenido: re-subir el mismo archivo devuelve existed: true.
  • Cuota (--max-gb): al no caber, el GC desaloja no-pineados más viejos; los pineados jamás se borran (si solo quedan pineados → 507).
  • TTL opcional por blob (?ttl=<ms>): vencido = candidato a GC.
  • El cifrado E2E es del lado del cliente (el node solo ve ciphertext si subes cifrado y marcas enc=1); la llave viaja en el #fragment del enlace.
  • owner y acl: con el node enlazado, todo lo que se sube queda estampado con el ownerId de tu bóveda (la mitad izquierda de la referencia compartible ownerId + cid). El acl nace privado: lo que no se marca public a mano no sale del node cuando llegue el modo público, y un blob cifrado no puede marcarse público (nadie sin la llave podría leerlo).

@dotrino/content-client (lo que usa una app)

El cliente de navegador vive en lib/ y se publica desde este mismo repo, igual que dotrino-vault publica su lib. Está aquí a propósito: el protocolo —los nombres de las ops, el tope de 256 KB, el formato de la referencia— es de las dos puntas, y separarlas en dos repos es la forma de que acaben diciendo cosas distintas.

import { ContentClient, buildUrl } from '@dotrino/content-client'

const cc = await ContentClient.connect({ link })   // link del vault: { id, cert, iss }
const ref = await cc.put(bytes, { mime: 'image/png' })   // cifra por defecto
const url = buildUrl(ref)      // https://eco.dotrino.com/#<owner>/<cid>/<llave>
const back = await cc.get(ref) // comprueba el hash antes de devolver nada
  • Cifra por defecto (§4): el node guarda ciphertext y la llave sale en la referencia, para el #fragment. Con encrypt: false queda en claro, que es lo que hace falta para poder marcarlo public y que tenga tarjeta.
  • connect() falla con code: 'no-node' si no tienes ninguno encendido, en vez de esperar. Es lo esperable y la app tiene que saber seguir sin node: al @dotrino/store va lo que debe estar siempre disponible; aquí van los bytes.
  • get() comprueba el hash de lo que llega: el cid es el hash, así que unos bytes que no cuadren se rechazan vengan de donde vengan.
  • Miniaturas (@dotrino/content-client/thumb): makeThumbnail() en canvas y putImageWithThumbnail(), que sube el original cifrado y privado y la miniatura en claro y pública — que es el reparto que hace que haya tarjeta sin publicar el archivo.
  • Lo PÚBLICO de otro usuario ya se lee por la red (fetchPublic en la lib, DISENO §16): un tercero con tu enlace encuentra tu node por el proxio y le pide el cid; el node contesta solo lo marcado público y en claro, y los bytes se verifican contra el cid. Lo privado sigue siendo de tus aparatos.

Tests

npm test

Fases

  1. Core local: blobs por cid, Range/206, índice, cuota+GC.
  2. Aparato del vault (este estado): enrolamiento, plano de control cifrado, owner + acl.
  3. Exposición: P2P/swarm por WebRTC + modo público HTTP opt-in + sembrador 24/7.
  4. Integración con eco (la app que resuelve el #fragment) + catálogo.

Licencia MIT.