It is a contracts library and not a batteries-included toolkit. nostr-tools is excellent at the latter and the two are not feature-for-feature competitors. What this exposes that nostr-tools does not is a hex-typed boundary the compiler can check, with PublicKey, EventId, RelayUrl, and Sig all branded, one Signer port that NIP-07 and NIP-46 and a local signer all satisfy, crypto failures returned as Results rather than thrown, and an HttpClient port so libraries that touch the network never reach for fetch directly. If you are building an app and do not need any of that, use nostr-tools. If you are working inside the innis stack or want swappable boundaries you can test against in memory, this is where the contracts live.
The standalone relay-selection library released earlier this month was the first piece of the TypeScript stack to go public. This is the foundation of everything else. The pool, the event store, the NIP-07 and NIP-46 signers, and the work built on top of all of it, all to follow as each layer is cleaned for release. The discipline I am working on is not letting that cleanup become the delay. The lesson keeps coming back around.
AI was involved, same terms as before. The architecture is mine. The decisions are mine. The machine held the other end of the board.
deno add jsr:@innis/nostr-core
https://github.com/johninnis/nostr-core-ts
MIT.
#nostr #typescript #opensource #nostrdev
quotingShipping two libraries today: nostr-relay-selection in TypeScript and PHP. They answer one question: given a Nostr event, which relays should it go to. Covers outbox publish routing, NIP-17 DMs, NIP-65 list parsing, author set-cover, e/p relay hints.
nevent1q…unvm
Both libraries are pure functions. No state, no I/O, no caches, no fallback URLs, no tie-breaks by Math.random or time decay. Same inputs, same outputs, every time.
The existing implementations (NDK's OutboxTracker, rust-nostr's gossip, go-nostr's sdk, welshman/router) are engines: stateful, heuristic, coupled to a pool. None are deterministic.
These libraries take the opposite trade. They are a spec. The TypeScript and PHP ports share a JSON corpus of test vectors. A vector that passes in one passes in the other. A Go or Rust port joins the same compliance suite or it doesn't conform.
If you're building a client, a relay, a bot, an indexer, the win is having routing live behind one auditable path. One place that knows the NIPs. One set of vectors that decide whether the answer is right. When NIP-65 grows a marker or NIP-17 tightens a rule, you update the library and every caller gets the new policy.
Post-spec decisions belong in an adapter at the boundary. Onion-only routing for a privacy build, dropping unresponsive relays based on pool state, home-relay ordering, scoring by past success — whatever your runtime knows that the spec doesn't. The library returns the NIP-derived answer; the adapter overlays your application's intuition on top.
In use across the rest of my Nostr stack, to be released soon. I'm also putting these out because I want them stress-tested by people who have shipped more Nostr than I have. If you spot a corpus (Claude's favourite word right now) vector that's wrong, a NIP rule I've misread, or a routing decision you'd make differently, the issue tracker is open.
deno add jsr:@innis/nostr-relay-selection
https://github.com/johninnis/nostr-relay-selection-ts
composer require innis/nostr-relay-selection
https://github.com/johninnis/nostr-relay-selection-php
MIT.
#nostr #typescript #php #opensource #nostrdev
