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

@thibmeu/dap

v0.1.0

Published

Distributed Aggregation Protocol (DAP) and Prio3 VDAFs in TypeScript

Readme

@thibmeu/dap

NPM License

TypeScript implementation of the Distributed Aggregation Protocol (DAP), as specified in draft-ietf-ppm-dap-19. Clients encrypt measurements, two aggregators verify and sum them without seeing any single value, and a collector gets only the aggregate.

Features

  • DAP draft 19: all four roles: Client, Leader, Helper, and Collector
  • VDAF draft 20: Prio3Count, Prio3Sum, and Prio3Histogram, checked against the published vectors
  • Web APIs: runs in browsers, Web Workers, Cloudflare Workers, and Node.js
  • Bring your own I/O: Client and Collector build Web Request objects and read Response objects; aggregators take and return bytes. HTTP, authentication, and storage stay with you

Installation

npm install @thibmeu/dap

Quick start

Create a task with the configuration agreed with your Leader and Helper, then report a measurement:

import { Client, HpkeConfigList, Task, prio3Count } from "@thibmeu/dap";

const task = Task.create({
  id: "8BY0RzZMzxvA46_8ymhzycOB9krN-QIGYvg_RsByGec",
  info: "page-views-v1",
  leader: "https://leader.example/",
  helper: "https://helper.example/",
  timePrecision: 3600,
  minBatchSize: 100,
  batchMode: "time-interval",
  vdaf: prio3Count(),
});

const client = await Client.create(task, {
  hpke: {
    leader: HpkeConfigList.parse(leaderHpkeConfigBytes),
    helper: HpkeConfigList.parse(helperHpkeConfigBytes),
  },
});
const report = await client.prepareReport(1);
const upload = client.prepareUpload([report]);
const result = await upload.process(await fetch(upload.request));

for (const { id, error } of result.rejected) console.warn(id, error);

task.encodeConfiguration() and Task.decode({ id, configuration }) move a task between services. prio3Sum(max) takes an integer from 0 to max, as a number or bigint. prio3Histogram(length, chunkLength) takes a bucket index below length, up to 4096 buckets.

Times are Unix milliseconds and report IDs are URL-safe Base64. Retry an uncertain upload with the same prepared report so the aggregators can drop the duplicate. Rotate aggregator keys with await client.withHpkeConfigs(hpke). Binary message codecs are in @thibmeu/dap/messages.

Collection

The Collector runs on a backend. Keep its HPKE private key and HTTP credentials there.

import { Collector } from "@thibmeu/dap";

const collector = await Collector.create(task, {
  configId: collectorConfigId,
  privateKey: collectorPrivateKey,
});
const day = Date.UTC(2026, 8, 28);
const prepared = collector.prepare({ start: day, end: day + 86_400_000 });
const request = prepared.request;
request.headers.set("authorization", `Bearer ${collectorToken}`);
const progress = await prepared.process(await fetch(request));

if (progress.status === "complete") console.log(progress.value); // 123n
else saveForLater(progress.state);

The interval must fall on the task's time precision. A pending job's progress.state is plain JSON; collector.resume(state) prepares the next poll. The result is a bigint for Count and Sum and a bigint[] for Histogram.

Aggregators

Leader and Helper run the DAP protocol steps on bytes. Your server routes HTTP, authenticates peers, and stores reports, jobs, and batch buckets.

import { Helper, Leader } from "@thibmeu/dap";

const leader = await Leader.create(task, {
  hpkeKeys: [{ configId: 1, privateKey: leaderHpkeKey }],
  verifyKeys: [{ id: 0, key: verifyKey }],
  collector: collectorHpkeConfig,
});
const helper = await Helper.create(task, {
  hpkeKeys: [{ configId: 1, privateKey: helperHpkeKey }],
  verifyKeys: [{ id: 0, key: verifyKey }],
  collector: collectorHpkeConfig,
});

// Leader, on upload: store accepted reports and answer with the rejections.
const incoming = leader.upload(body);
const replayed = incoming.reports.filter(seenBefore);
store(incoming.reports.filter((r) => !seenBefore(r)));
reply(incoming.respond(replayed.map(({ id }) => ({ id, error: "report-replayed" }))));

// Leader, later: build a job. Persist request and state before sending.
const job = await leader.prepare(storedReports);

if (job.request) {
  // Helper: verify, commit the output shares, then seal the response.
  const verified = await helper.verify(job.request);
  for (const r of verified.reports)
    if (r.outputShare) updateBucket(r.time, (b) => helper.addToBucket(b, r));
  const response = verified.seal();

  // Leader: finish from the saved state and commit each output share once.
  for (const r of leader.finish(job.state, response))
    if (r.outputShare) updateBucket(r.time, (b) => leader.addToBucket(b, r));
}

A batch bucket is one opaque Uint8Array per report time; pass undefined to start one. For collection, leader.collection(body) and helper.aggregateShare(body) return the requested interval. Merge its buckets with mergeBuckets(), then call the returned job's finish().

Your storage must enforce what the library cannot see: reject replayed reports and reports for collected batches, commit output shares together with the response bytes, answer retries from those bytes, and keep batches with pending jobs out of collection.

Serve each role's HPKE list from role.hpkeConfigs. List keys most preferred first and keep a retired key for twice the configuration's cache lifetime.

Errors

Request-level failures throw DAPError. When a peer answers with an RFC 9457 problem document, error.problem.dapError holds its DAP error type, such as invalidBatchSize. On a server, problemResponse(error, task.id) turns an error into that response and hides errors this library did not raise.

Security considerations

Not audited. Use at your own risk.

Correctness is tested against the published VDAF and HPKE vectors, with hand-derived DAP message fixtures and malformed-input tests.

DAP protects measurements only while the Leader and Helper do not collude. It does not add differential privacy, and a small batch can reveal individual values: set minBatchSize accordingly.

DAP hides what a client reported, not that it reported. The Leader sees each upload's source IP, arrival time, and task ID (draft-ietf-ppm-dap-19, Section 8). If that is sensitive, send reports through an Oblivious HTTP relay (Section 8.4) or report on a fixed schedule.

License

MIT. Specification test vectors keep their IETF notices.