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

@olurabian/receipt

v0.3.1

Published

The Deadlatch receipt. A zero-dependency, hash-chained, independently verifiable record envelope shared by Purse, blackbox and Tripwire.

Readme

@olurabian/receipt

The Deadlatch receipt. A zero-dependency, hash-chained record envelope that a third party can verify without trusting the writer. It is the engine under Purse (kind: "decision") and blackbox (kind: "action"), published on its own so anyone can verify a chain with one install.

npm i @olurabian/receipt

This package is ESM only and needs Node 18 or newer. Use import, not require.

import { makeReceipt, verifyChain, JsonlStore } from "@olurabian/receipt";

const store = new JsonlStore("receipts.jsonl");
makeReceipt(store, { kind: "decision", payload: { payee: "api.stripe.com", amount: 1200, status: "allowed" } });

verifyChain(store.all()); // { ok: true }
// after any edit, insert, removal, or reorder:
// { ok: false, brokenAt: 0, id: "…", reason: "hash mismatch (a record was altered)" }

Truncation at the tail is the one edit a chain cannot detect on its own. That is what an outside witness anchoring the chain head is for.

Verify a chain using nothing but node:crypto and node:fs, with no dependency on this package.

import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";

const receipts = JSON.parse(readFileSync("receipts.json", "utf8"));
const sha256 = (s) => createHash("sha256").update(s).digest("hex");

let prev = "0".repeat(64);
let ok = true;
receipts.forEach((r, i) => {
  const { id, ts, kind, payload, prevHash, hash } = r;
  const expect = sha256(JSON.stringify({ id, ts, kind, payload, prevHash }));
  if (prevHash !== prev || hash !== expect) { console.log("broken at index", i, r.id); ok = false; }
  prev = hash;
});
console.log("ok", ok);

Postgres

For real deployments, PostgresStore keeps the same synchronous Store interface and writes every receipt through to Postgres in order. It takes any client with a query(text, params) method, so the package stays dependency free. pg and PGlite work as they are. Neon works over its Pool, or over HTTP when you ask for full results.

import pg from "pg";
import { PostgresStore, makeReceipt } from "@olurabian/receipt";

const client = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const store = await PostgresStore.open(client, { stream: "purse" });

makeReceipt(store, { kind: "decision", payload: { status: "allowed" } });
await store.flush(); // resolves once the receipt is durable
// Neon over WebSockets. On Node 18 also set neonConfig.webSocketConstructor = ws.
import { Pool } from "@neondatabase/serverless";
const client = new Pool({ connectionString: process.env.DATABASE_URL });

// Neon over HTTP
import { neon } from "@neondatabase/serverless";
const client = neon(process.env.DATABASE_URL, { fullResults: true });

With postgres.js, wrap it once.

const client = { query: (text, params = []) => sql.unsafe(text, params).then((rows) => ({ rows })) };

What open does. It creates the table if it is missing, loads the stream in order, verifies the chain, and refuses to start on a broken one. One table holds many streams, one per product.

One writer per stream. The table has a unique constraint on the previous hash, so a second process appending on the same head is rejected by the database rather than corrupting the chain. Run one writer per stream.

Degraded means stopped. A transient insert failure is retried three times. A hard failure, including a rejected fork, latches the store. After that append() throws, flush() rejects, and pending() reports the backlog. Fix the database and restart the process, and open reloads and re-verifies. Read degraded() for the error. The constraint name tells you what happened. _stream_prev_hash_key is a fork, another writer landed on the same head. _stream_id_key is your own write, the insert committed but the reply was lost, and a restart reloads it cleanly. The whole stream is held in memory, so memory and start-up time grow with the stream.

Reload reads the canonical text. Postgres reorders keys inside jsonb, which would break the hash on reload, so the store keeps the exact receipt text in a record column and uses payload only for SQL queries.

Only record is covered by the hash. A SQL query over payload is convenience, not evidence. Someone with write access to the table can change payload without touching the chain. Verify with PostgresStore.open() or verifyChain before you rely on a number you read with SQL.

What is durable when. append() returns before the insert commits. If the process dies before the queued inserts land, everything pending() counts is lost, and nothing else. What remains in Postgres still verifies, because the loss is a tail. A crash and a deliberate truncation look the same to a verifier, so anchor the chain head if that matters to you. The JSONL store has a smaller window, one synchronous write, but it cannot survive the machine and cannot reject a fork. Wait on flush() whenever you need the guarantee.

The receipt

{ id, ts, kind, payload, prevHash, hash }

hash = sha256( JSON.stringify({ id, ts, kind, payload, prevHash }) ) in exactly that key order. prevHash of the first receipt is 64 zeros. That is the whole spec. Match it byte for byte in any language and you can verify a Deadlatch chain with nothing from us.

Receipts are produced by JavaScript's JSON.stringify, so a verifier written in another language must match its exact conventions.

  • No whitespace
  • UTF-8 output, non-ASCII characters left unescaped
  • Only control characters, quotes, backslashes, and lone surrogates escaped
  • JavaScript's key ordering, where integer-like keys sort first in numeric order

Python's default json.dumps differs on two of these. It adds whitespace after separators, and it escapes non-ASCII characters unless ensure_ascii=False is set.

verifyChain walks the chain and reports the index and id of the first receipt that fails, and why.

Anchoring the head

A chain in your own hands is tamper-evident to you and meaningless to everyone else, because whoever holds the whole log can rebuild it. The anchor subpath commits the head to a public transparency log and lets anyone verify the chain against those commitments with nothing from the writer.

import { RekorV2, P256Signer, verifyAnchored, PostgresAnchorStore } from "@olurabian/receipt/anchor";

const signer = P256Signer.fromPem(readFileSync(process.env.WITNESS_KEY_FILE, "utf8"));
// REKOR_LOG_KEY is <origin>=<base64 DER>
const [origin, logKey] = process.env.REKOR_LOG_KEY.split("=");
const rekor = new RekorV2({ url: process.env.REKOR_URL, logKeys: [{ origin, publicKey: logKey }] });
const anchor = await rekor.submit("purse", records.length - 1, records.at(-1).hash, signer);   // verified before it returns
await anchors.append(anchor);

const result = verifyAnchored(records, await anchors.list("purse"), { logKeys: [...], witnessKeys: [signer.publicKeyDer()] });
// { ok, coveredUpTo, anchors: [{ seq, ok, reason?, logIndex, cosigned }], chain, stream }

What an anchor proves. Everything at or below coveredUpTo is what it was when the log recorded the head. A rewrite there breaks the anchor and is named by seq. A truncation there is a missing record and is named too. Above coveredUpTo the chain is tamper-evident only. Order is proven by the log index. Wall-clock time is not, the at field is the witness clock. Read ok first, because a break anywhere in the chain, not only at or below coveredUpTo, makes ok false. ok true with coveredUpTo null means nothing was ever anchored, and the command line exits 1 for that reason alone.

The two keys you pin. This package ships neither.

  1. The log key. In the sigstore/root-signing repository, targets/trusted_root.json, the tlogs entry whose baseUrl is your REKOR_URL, field publicKey.rawBytes. Its C2SP key id, checkpointKeyId(origin, key), must equal the logId.keyId a reply carries. That trust root entry also carries validFor, the window the key is good for, which is what lets an anchor made under a since-rotated key still verify.
  2. The witness key. The witness prints its public key on first run and serves it on GET /. Pin it the way you pin an SSH host key. A rotated witness is a new key; old anchors stay valid under the old one.

The verifier from the command line, with the chain from the broker's audit endpoint and the anchors from a witness.

npx -p @olurabian/receipt receipt-verify chain.jsonl --anchors https://witness.example.com \
  --log-key log2025-1.rekor.sigstore.dev=<base64 DER> \
  --witness-key <base64 DER> \
  --stream purse

--stream is optional and binds the check to that stream, so an anchor from another stream fails loudly instead of silently passing on its own. Omit it and every anchor still has to agree on one stream.

Exit 0 when ok and at least one anchor verified, 1 when the chain fails, an anchor fails, or nothing anchored at all, 2 on a usage or read error. The public instance's URL rotates by year, so configure it, never compile it in. Only Ed25519 log keys are supported for checkpoints in this version.

Part of Deadlatch, enforce, prove, watch. MIT.