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/reputation

v0.9.0

Published

Cliente del registro de reputación de Dotrino (backend rep.dotrino.com): atestaciones firmadas sobre cualquier sujeto (perfil/dominio/handle) + preguntas/respuestas + agregación anti-sybil anclada en web-of-trust

Readme

@dotrino/reputation

Parte del ecosistema Dotrino. Dotrino es un ecosistema de aplicaciones centradas en la privacidad de los datos: tu información es tuya, y las decisiones sobre ella también — qué compartes, con quién, cuándo y por qué. Sin anuncios, sin cookies, sin rastreo de datos, sin vender tu identidad a nadie.

Registro de reputación del ecosistema Dotrino (backend rep.dotrino.com; la app pública vive en reputation.dotrino.com). Quinto pilar, complementario a identidad, transporte (proxy), almacenamiento (store) y descubrimiento (geo).

| Pilar | Paquete | Rol | |-------|---------|-----| | Identidad | dotrino-identity | clave del vault, firma, web-of-trust local | | Transporte | dotrino-proxy-client | mensajería, canales, WebRTC (efímero) | | Almacenamiento | dotrino-store | datos del usuario en su navegador | | Descubrimiento geo | dotrino-geo | encontrar identidades cercanas (efímero) | | Reputación | dotrino-reputation | atestaciones firmadas compartidas (persistente) |

El registro es un tablón de atestaciones firmadas, no un juez

A diferencia de geo/proxy (efímeros), este registro es persistente — pero lo que guarda es opinión pública firmada, no datos privados de peers. Cada fila es { subject, issuer, rating 0–5, notes?, ts, receipt? } firmada por el emisor: el server no puede falsificarla y, si desaparece, las atestaciones siguen donde cada peer las cacheó.

El server NO calcula un score (eso sería trivial de atacar con sybils). Sólo almacena y sirve las atestaciones crudas. El significado se computa en el cliente.

Anti-sybil: la confianza está anclada en vos

aggregateTrust(subject, { trustOf, ... }) pondera cada atestación por cuánto confiás VOS en quien la emite (web-of-trust transitivo con decaimiento por distancia). Consecuencias:

  • Un flood de bots desconocidos vale ~0: no podés rastrearlos a nadie en quien confiás → no mueven la aguja. No hay número global que mover.
  • Defiende las dos direcciones: auto-inflado (sybils que se suben el rating) y review-bombing (sybils que hunden a un objetivo).
  • Cold-start honesto: un vendedor nuevo legítimo también arranca sin score para vos (mismo mecanismo). Mostrá el conteo crudo etiquetado aparte como señal débil.

La confianza personal (trustOf) sale del web-of-trust local del vault (getRatingsForSubject/agregación). Local = mi opinión; registro = atestaciones compartidas y consultables.

Instalación

npm i @dotrino/reputation

Uso

import { createReputationClient } from '@dotrino/reputation'
import identity from '@dotrino/identity' // el vault

const rep = createReputationClient({
  signData: identity.signData,
  getPublicKeyJwk: identity.getPublicKeyJwk
})

// Calificar a un peer tras un trato (opcionalmente con recibo co-firmado)
await rep.publishRating({ subject: sellerPubkey, rating: 5, notes: 'todo ok' })

// Ver la reputación del peer, ponderada por MI web-of-trust
const r = await rep.aggregateTrust(sellerPubkey, {
  trustOf: async pk => myVaultTrustOf(pk),   // 0..1, o null si no tengo opinión
  myPubkey: await identity.getPublicKeyJwk()
})
// r = { score: 0.92|null, confidence, trustedCount, rawCount, txBoundCount, samples }
// score null + rawCount alto  => "muchas reseñas, ninguna de tu red" (señal débil)

Multiindicador (v0.3.0)

Cada atestación lleva un mapa de indicadores independientes (entero 0..5):

await rep.rate(peerPubkey, { confianza: 5, afinidad: 3 })   // o rep.rate(pk, 5) → confianza
const r = await rep.reputationOf(peerPubkey)
// r.indicators.confianza.score, r.indicators.afinidad.score  (cada eje independiente)
// r.score === r.indicators.confianza.score  (compat)
  • confianza es el eje especial: es el ancla anti-sybil con la que se ponderan TODOS los indicadores (la credibilidad transitiva se calcula sobre confianza). Los demás ejes (afinidad = me interesa/sigo/conozco, o los que la app defina) se atestan y agregan igual, pero no gatean credibilidad.
  • Compat: rating (número) ↔ indicators.confianza; el top-level score del resultado sigue siendo confianza. Apps 0.2.x siguen funcionando.

Integración de 1 línea con el vault (recomendado para apps)

Las apps del ecosistema ya usan el web-of-trust local del vault. createVaultReputation cablea solo el trustOf desde ahí y mantiene local + nube en sync:

import { createVaultReputation } from '@dotrino/reputation'
const rep = createVaultReputation(identity)   // identity = vault conectado

await rep.rate(peerPubkey, 5, { notes: 'buen trato' })  // guarda local + atesta firmado
const r = await rep.reputationOf(peerPubkey)             // ponderado por MI web-of-trust → badge
const mine = await rep.myIndicatorsFor(peerPubkey)       // { confianza: 5, afinidad: 4 } → repoblar MIS controles

identity debe exponer me.publickey, signData y getRatingsForSubject (la instancia de @dotrino/identity). El paquete no depende de identity: se la inyectás.

Releer lo que YO califiqué: myIndicatorsFor (v0.7.0)

Cada eje es una atestación independiente (un registro firmado por canal), así que las atestaciones que devuelve getRatings traen un indicators de una sola clave. Releer lo tuyo con find te da un solo eje y los demás aparecen en 0:

// ❌ MAL: `mine.indicators` es { confianza: 5 } — afinidad y conoce se pierden
const mine = attestations.find(a => a.issuer === myPubkey)

// ✅ BIEN: fusiona todas mis atestaciones (y compara con samePubkey)
const ind = await rep.myIndicatorsFor(subject)          // { confianza: 5, afinidad: 5, conoce: 5 }
// o, si ya tenés las atestaciones a mano:
import { myIndicators } from '@dotrino/reputation'
const ind2 = myIndicators(attestations, myPubkey)

Ojo: los ejes de un mismo rate() se emiten en paralelo y comparten ts, y el servidor ordena por ts DESC — el empate hace que find devuelva un eje arbitrario. myIndicators(attestations, issuer) también sirve para agrupar las atestaciones de otro emisor (una fila por persona, no una por eje).

Recibo de transacción (opcional, sube credibilidad)

Una atestación puede llevar un recibo co-firmado { a, b, ts, sigA, sigB } (ambas partes firman {op:'receipt',a,b,ts}). Prueba que hubo una interacción real entre emisor y sujeto; marca la atestación txBound. No reemplaza al anclaje de confianza (una granja de sybils puede co-firmarse recibos), pero da un conteo de interacciones reales y sube el costo del fraude.

Sujetos: cualquier cosa única, no solo perfiles (v0.6.0)

El subject no tiene que ser un perfil (pubkey): puede ser un dominio, un handle de red o un correo. subjectRef lo normaliza a una cadena canónica estable — misma cosa ⇒ mismo subject_id — para que todos apunten a la misma fila.

import { subjectRef, detectSubjectType } from '@dotrino/reputation'
await subjectRef('domain', 'https://www.Example.com/') // → 'domain:example.com'
await subjectRef('x', '@dotrino')                       // → 'x:dotrino'
await subjectRef('email', '[email protected]')            // → 'email:<sha256>' (nunca en claro)
// el perfil va como su JWK tal cual (compat con las calificaciones existentes)
detectSubjectType('github.com/imdotrino')               // → 'github'
detectSubjectType('@dotrino')                           // → null: ¿de qué red? (v0.8.0)

detectSubjectType sólo resuelve lo inequívoco (un JWK, un correo, una URL del servicio, un dominio). Un handle pelado (@juan) devuelve null a propósito: puede ser de X, de LinkedIn o de GitHub — sujetos distintos, y casi seguro personas distintas. Hasta la 0.7 se asumía X en silencio, así que quien escribía @juan pensando en LinkedIn terminaba calificando a otro. Si te da null, pregunta de qué red es (un selector de tipo) y llama a subjectRef(tipo, valor), que acepta el handle con o sin @, o la URL.

Sólo el emisor debe ser una pubkey (firma). El peso anti-sybil se ancla en tu confianza en los EMISORES, así que aggregateTrust/reputationOf funcionan igual sea el sujeto un perfil, un dominio o un handle.

Preguntas y respuestas ordenadas por reputación (v0.6.0)

Cualquiera adjunta preguntas a un sujeto y cualquier perfil las responde (una respuesta por perfil). rankQuestions ordena por la credibilidad sumada de quienes responden: una respuesta de reputación real en tu web-of-trust pesa más que millones de desconocidos (reusa el mismo motor credibility que las calificaciones).

const rep = createVaultReputation(identity)
const subject = await subjectRef('domain', 'tienda.com')
const { questionId } = await rep.postQuestion(subject, '¿Es confiable?')
await rep.answer(questionId, 'Sí, compré y llegó')
const ranked = await rep.rankQuestions(subject)
// ranked[i] = { question, weight, weightedAnswerers, rawAnswerCount, answers[] }

API del servicio (HTTP/JSON, backend rep.dotrino.com)

| Método | Ruta | Auth | Descripción | |--------|------|------|-------------| | PUT | /ratings | sobre firmado por el emisor | publica/reemplaza una atestación (op:'rate' por canal) | | DELETE | /ratings | sobre firmado | retira la atestación (con channel = un solo eje) | | GET | /ratings?subject=<ref> | pública | atestaciones crudas sobre un sujeto | | PUT | /questions | sobre firmado | publica una pregunta → { questionId } | | DELETE | /questions | sobre firmado | retira la propia pregunta | | GET | /questions?subject=<ref> | pública | preguntas crudas de un sujeto | | PUT | /answers | sobre firmado | publica/reemplaza la propia respuesta | | DELETE | /answers | sobre firmado | retira la propia respuesta | | GET | /answers?question=<id> | pública | respuestas crudas a una pregunta | | GET | /health | — | liveness |

Ver server/ y DEPLOY.md. Independiente de geo: base Postgres propia, puerto propio, despliegue propio (escalable a otro server).

Licencia

MIT