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

tr-json-chain-tools

v0.5.0

Published

Command-line tools for tr-json-chain: a CLI to record into and inspect a hash-chained JSON event log, and a standalone CSV-export integrity verifier.

Readme

tr-json-chain-tools

Command-line tools for tr-json-chain — an immutable, append-only, SHA-256 hash-chained JSON event log on PostgreSQL.

Four executables:

| command | what it does | |---|---| | tr-json-chain-cli | record events into a chain and inspect it (interactive or piped) | | tr-json-chain-check | verify the integrity of a chain exported to CSV — standalone, no database | | tr-json-chain-seal-keygen | generate a seal key pair (JWKs) to files | | tr-json-chain-scheduler | a server that appends periodic timestamp and seal events to a chain |

Install

npm install -g tr-json-chain-tools

Requires Node.js ≥ 24 (the seal tooling uses tr-jwt / tr-jwk).

or run without installing:

npx -p tr-json-chain-tools tr-json-chain-cli  postgres://… mychain
npx -p tr-json-chain-tools tr-json-chain-check export.csv

A PostgreSQL to play with

A throwaway server is handy for trying things out. A compose file ships under dc/:

docker compose -f dc/docker-compose.yml up -d
# → postgres on localhost:5433, user/password postgres/postgres

tr-json-chain-cli

tr-json-chain-cli --pg-url <uri> [--namespace <ns>] [--init] [--verify]
                  [--seal-key <jwk> | --seal-key-file <path>]

| option | env | description | |---|---|---| | --pg-url <uri> | OPT_PG_URL | PostgreSQL connection URI (required) | | --namespace <ns> | OPT_NAMESPACE | chain namespace, so one database can hold several independent chains (<ns>_event_chain, …); omit for bare names | | --init | OPT_INIT | create/initialize the chain if it does not exist (the CLI never creates a chain silently) | | --verify | OPT_VERIFY | on startup, verify the entire chain server-side ({ verifyChain: true }) instead of only the root-event canary | | --seal-key <jwk> | OPT_SEAL_KEY | a public seal JWK as inline JSON (see below) | | --seal-key-file <path> | OPT_SEAL_KEY_FILE | a public seal JWK read from a file | | -h, --help | | show help and exit |

Every option may be supplied via its environment variable instead of the flag (booleans accept yes/no), which makes the tool convenient to drive from a container or service manager. --help lists everything.

Initialization. The CLI no longer creates a chain implicitly. On an uninitialized chain it exits with an error unless --init is given; with --init it creates the schema and root event. An already-initialized chain is used as-is.

Seal key. --seal-key / --seal-key-file (mutually exclusive) supply a public seal JWK — typically the seal-public.jwk from tr-json-chain-seal-keygen. On --init it is embedded in the root event (sealKey) per the canonical-events spec; on an already-initialized chain it is verified against the root (the public key must match; kid/alg are checked when present). Any mismatch — or a failed initialization — is printed and the CLI does not start.

Each input line is either:

  • a JSON object → recorded as an event; its event_id is printed (hex); or

  • a slash command:

    | command | action | |---|---| | /timestamp | record a { "type": "ts", "ts": … } event | | /head | fetch the chain head (appends an empty checkpoint if needed) | | /root | print the chain's root event (id + stored data) | | /lc [<start> [<end>]] | list the chain (getEvents() slice indices; default = all) | | /cc | check chain integrity fully client-side (re-hash every event) | | /export <filename> | export the chain (minus genesis) to a semicolon CSV | | /help | list commands | | /exit | quit |

When stdin is a terminal it runs as an interactive line editor with command history (up/down) and TAB completion; with piped input it runs as a plain batch processor. A non-object line, malformed JSON, or unknown command prints an error and sets a non-zero exit code.

# interactive
tr-json-chain-cli --pg-url postgres://postgres:postgres@localhost:5433/postgres --namespace demo

# batch
printf '%s\n' '{"type":"user.login","user":42}' '/timestamp' '/cc' \
  | tr-json-chain-cli --pg-url postgres://postgres:postgres@localhost:5433/postgres --namespace demo

# same, configured entirely from the environment
export OPT_PG_URL=postgres://postgres:postgres@localhost:5433/postgres OPT_NAMESPACE=demo
tr-json-chain-cli

By convention every event carries a top-level "type" discriminator (the chain's own root and timestamp events do); it is not required.

CSV export format

/export <file> writes a semicolon-separated CSV, genesis row excluded, in sequence order starting at #1:

#;event_id;parent_id;data_hash;data
  • # — the event's sequence number (event_chain.id).
  • event_id / parent_id / data_hash — lowercase hex.
  • data — the canonical payload text (exactly the bytes that were hashed), RFC-4180 quoted; empty for events with no stored payload.

This is precisely what tr-json-chain-check consumes.

tr-json-chain-seal-keygen

Generates a seal key pair and writes the two JWKs to files: a public key to embed in a chain's root (tr-json-chain-cli --seal-key) and a secret key to sign seals with (tr-json-chain-scheduler --seal-secret-key).

tr-json-chain-seal-keygen [--alg <alg>] [--kid <kid>] [--secret <file>] [--public <file>] [--force]

| option | env | description | |---|---|---| | --alg <alg> | OPT_ALG | ES256 (default), ES384, ES512, RS256, RS384, RS512 | | --kid <kid> | OPT_KID | key id stored in both JWKs (default: a random UUID) | | --secret <file> | OPT_SECRET | output path for the private JWK (default seal-secret.jwk, mode 0600) | | --public <file> | OPT_PUBLIC | output path for the public JWK (default seal-public.jwk) | | --force | OPT_FORCE | overwrite existing output files |

tr-json-chain-seal-keygen --alg ES256 --secret seal-secret.jwk --public seal-public.jwk

The secret file is written with mode 0600; existing files are not overwritten without --force. The supported algorithms are a deliberate subset of the canonical seal set — PS* and ML-DSA-* are not offered here.

tr-json-chain-scheduler

A long-running server that appends periodic ts and seal events to a chain. Seals are signed (tr-jwt) with a private seal JWK whose public half must be embedded in the chain root.

tr-json-chain-scheduler --pg-url <uri> [--namespace <ns>]
                        [--seal-secret-key <jwk> | --seal-secret-key-file <path>]
                        [--seal-interval <seconds>] [--timestamp-interval <seconds>]
                        [--init-chain] [--verbose]

| option | env | description | |---|---|---| | --pg-url <uri> | OPT_PG_URL | PostgreSQL connection URI (required) | | --namespace <ns> | OPT_NAMESPACE | chain namespace; omit for bare names | | --seal-secret-key <jwk> | OPT_SEAL_SECRET_KEY | a private seal JWK as inline JSON | | --seal-secret-key-file <path> | OPT_SEAL_SECRET_KEY_FILE | a private seal JWK read from a file | | --seal-interval <s> | OPT_SEAL_INTERVAL | seconds between seals (requires a seal secret key) | | --timestamp-interval <s> | OPT_TIMESTAMP_INTERVAL | seconds between timestamp events | | --init-chain | OPT_INIT_CHAIN | create the chain if absent (else exit with an error) | | --verbose | OPT_VERBOSE | print the data of each event recorded |

At least one of --seal-interval / --timestamp-interval must be given. On startup the server applies the same root-key check as the CLI when a seal key is supplied (public key must match; kid/alg if present); a mismatch — or an uninitialized chain without --init-chain — is fatal. It records events until it receives SIGINT/SIGTERM, then drains and exits cleanly.

# heartbeat every minute, seal every hour
tr-json-chain-scheduler --pg-url postgres://postgres:postgres@localhost:5433/postgres \
  --namespace demo --seal-secret-key-file seal-secret.jwk \
  --timestamp-interval 60 --seal-interval 3600 --verbose

The seal event embeds a signed JWT whose chain-op: "seal" marker is a payload claim (per tr-json-chain ≥ 1.0.2); the JWT names the sealed event_id and the chain identity, attesting that the chain reached that point under the seal key.

tr-json-chain-check

tr-json-chain-check <file.csv>

A standalone, self-contained verifier (only Node builtins — no database, no tr-json-chain dependency). It re-derives every hash from the spec, so it also serves as a compact reference implementation of the chain's integrity rules:

  • data_hash == SHA256(utf8(data)) for every row whose payload is present;
  • event_id == SHA256(parent_id ‖ data_hash) for every row;
  • each parent_id equals the previous row's event_id, and # is contiguous.

It accepts a partial chain (a contiguous slice not starting at the root): the first row then has # ≠ 1 and a non-zero parent_id, and the summary reads Partial chain OK instead of Chain OK.

The data column is optional: if absent, no event carries a payload; an empty cell means that event's payload is unavailable.

$ tr-json-chain-check export.csv
Chain OK: 1502 events verified, payload 1501/1501 (100.0%) present, in 12.3 ms

The n/m figure is payloads-present over events that have a real (non-zero) data_hash — head/checkpoint placeholder events are excluded from the denominator. Exit code is 0 on success, 1 on an integrity failure (printed as INVALID: <reason>), 2 on a usage/IO error.

See also

  • tr-json-chain — the library these tools operate on, including the full hash specification and the language-agnostic verification algorithm.

Author

Timo J. Rinne [email protected]

License

MIT