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

zod-nostr

v0.7.0

Published

Spec-faithful, tunable Zod schemas & codecs for Nostr — strict by default, loosen deliberately. Classic + zod/mini.

Downloads

377

Readme

zod-nostr

npm version CI codecov provenance types license

Spec-faithful, tunable Zod schemas & codecs for Nostr — strict by default, loosen deliberately.

  • Strict, spec-faithful atoms — each schema validates to exactly what its NIP permits, and never rejects spec-valid input.
  • Tunable in both directions — strict bases compose with zod's own loosening tools (optional, catch, default, refine) in each flavor's native form, so you can loosen them deliberately for the messy data real relays serve.
  • Classic zod and zod/mini — one set of rules, written once against zod/v4/core and re-exposed through classic zod and tree-shakeable zod/mini with each flavor's native .check() chaining.
  • Precise type inference — schemas are the single source of truth, so inferred types and runtime checks can't drift apart.
  • Bidirectional codecs — NIP-19 bech32 entities and NIP-21 nostr: URIs decode and encode, not just validate.
  • Opt-in checks — signatures, proof of work, expiration, and authentication compose via .check() instead of being baked into every parse.
  • Framework-agnostic — a pure schema layer you can drop into any Nostr stack.

Covers NIP-01, NIP-05, NIP-10, NIP-11, NIP-13, NIP-19, NIP-21, NIP-24, NIP-40, NIP-42, NIP-45, NIP-50, NIP-67, and NIP-70 — see Supported NIPs.

Installation

npm install zod-nostr zod

zod (^4.4.3) is a peer dependency — bring your own version.

zod-nostr ships as ESM only.

Quick start

classic zod

import { z } from "zod";
import { zostr } from "zod-nostr";

const schema = z.object({ pubkey: zostr.pubkey() });
schema.parse({ pubkey: "3bf0c63f..." });

// Structure only, no signature check:
zostr.event().parse(someEvent);

// Structure + signature verification, composed explicitly:
zostr.event().check(zostr.signatureCheck()).parse(someEvent);

zod/mini

import * as z from "zod/mini";
import { zostr } from "zod-nostr/mini";

const schema = z.object({ pubkey: zostr.pubkey() });
z.parse(schema, { pubkey: "3bf0c63f..." });

z.parse(zostr.event().check(zostr.signatureCheck()), someEvent);

The zostr object exposes the identical set of functions from both entry points — only the import path and the ambient zod flavor differ.

Every API has one canonical owner path — usually its spec namespace (zostr.nip19.npub()), a domain namespace for a cross-spec catalog (zostr.nip01.metadataFields.*), or the root for a cross-spec utility (zostr.jsonCodec()). Frequently used Nostr-wide concepts are also re-exposed at the root as an ergonomic alias that is a direct reference to the same factory:

zostr.event(); // alias of zostr.nip01.event()
zostr.event === zostr.nip01.event; // true

Design notes

These notes summarize a few user-facing choices. The full public-API design principles — controllability, strict atoms, opt-in checks, versioning, and the verification bar for new APIs — live in docs/design.md.

Why two entry points?

zod v4 ships two API flavors: classic zod (chainable methods, z.string().min(1)) and zod/mini (functional composition, z.string().check(z.minLength(1)), optimized for tree-shaking). They don't share method chains, but both build on the same schema representation in zod/v4/core.

zod-nostr's validation logic is written once against that core, and each entry point re-wraps it through its own flavor's native z.object(). That is what lets each flavor's own composition — classic's .optional(), mini's z.optional() — work on the schemas returned, with no custom sugar in between.

Signature verification is opt-in, via .check()

zostr.event() validates NIP-01 event structure (field shapes, hex lengths, tag shape) but does not verify the signature. Verifying every event is comparatively expensive, so forcing it into every .parse() would be a poor default for bulk ingestion paths that don't need it. Compose it explicitly, in zod's own check-composition style rather than a bespoke chain method:

zostr.event().check(zostr.signatureCheck())

bech32 format check vs. codec

  • zostr.bech32(prefix) — validates a well-formed bech32 entity with the given prefix and returns the string as-is.
  • zostr.npub(), zostr.nsec(), … — full codecs: z.decode() to the underlying value, z.encode() back to the string (or .decode()/.encode() on the classic schema). nsec() decodes to raw bytes (Uint8Array), not hex, matching how nostr-tools represents secret keys elsewhere.

Supported NIPs

| NIP | Coverage | | --- | --- | | NIP-01 | Events and templates, opt-in signature verification, kind:0 profile metadata (content codec and field atoms), the REQ/COUNT filter, and relay/client messages | | NIP-05 | Identifiers and the .well-known/nostr.json document | | NIP-10 | kind:1 text notes, marked reply and citation tags, opt-in reply and thread checks | | NIP-11 | Relay information document | | NIP-13 | Proof of work: the nonce tag, opt-in achieved-difficulty and commitment checks | | NIP-19 | bech32 entities, as codecs and as a validation-only format check | | NIP-21 | nostr: URIs over the NIP-19 entities except nsec: validation-only, per-entity codecs, and a union that decodes any of them | | NIP-24 | Extra kind:0 profile fields (display_name, website, banner, bot, birthday), alongside NIP-01's | | NIP-40 | Expiration timestamps: the expiration tag and an opt-in not-expired check | | NIP-42 | Authentication: the kind: 22242 event, the AUTH messages, opt-in checks | | NIP-45 | Event counts: the COUNT request and response, and the response body | | NIP-50 | Search: the filter extended with search, an intentional superset of NIP-01's | | NIP-67 | EOSE completeness hints: EOSE extended with an optional hints array | | NIP-70 | Protected events: the ["-"] tag and an opt-in authenticated-author check |

Each NIP links to its current text. Which revision these schemas are written against is recorded in spec-baseline.json — the upstream commit, when it landed, and the SHA-256 of the document, so an entry can be confirmed against the document rather than taken on trust. It also covers the two LUD specs behind nip01.metadataFields.lud06() and lud16() — LUD-01, which owns the LNURL encoding the lud06 field carries, and LUD-16.

See docs/API.md for the full reference and docs/guides.md for task-oriented guides.

Development

npm run typecheck    # tsc --noEmit
npm run check        # biome check . (lint + format check)
npm run check:write  # biome check --write . (auto-fix)
npm test             # vitest run
npm run build        # emit dist/ (classic.js + mini.js)

CI (.github/workflows/ci.yml) runs all of the above on every push and pull request to main.

The conventions those commands cannot check — commit marking, changelog format, which document a change belongs in, what a new schema owes its specification — are in CONTRIBUTING.md.

Release process

Versioning follows docs/design.md: before 1.0, backward-incompatible public API changes bump the minor version, and backward-compatible additions and fixes bump the patch version.

The steps are in RELEASING.md.

License

MIT