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

@estiva-app/platform

v0.3.1

Published

Browser lifecycle two Estiva apps share: the one live relay socket a tab should hold, its credential read per connect, the network coming back, and when a tab re-reads.

Readme

@estiva-app/platform

The browser lifecycle two Estiva apps share.

@estiva-app/protocol owns the wire — event construction, ids, signatures, and the two relay clients — and deliberately contains no fold. This package sits above it and holds the things that are about running in a tab: the one socket a tab should have, its credential read on every connect, and the network coming back.

npm install @estiva-app/platform

@estiva-app/protocol is a peer dependency.

Why it exists

createLiveRelay and createChannelSubscriptions were extracted into protocol with a single caller, and the plumbing a second app needs never moved with them. It stayed in peek-app/src/nostr/liveTopics.ts, which ADR 0002 §10 scores ❌ on constraint 1 (imports) and ❌ on constraint 2 (environment injected), and calls "the bill SHA-7 is paying".

Three behaviours, each of which fails as a healthy-looking socket that delivers nothing:

  • One instance per tab. max_subscriptions is per connection and the relay authenticates a connection once, so a connection per component mount is wrong rather than merely wasteful. React StrictMode makes it concrete: anything owned by an effect is built twice.
  • The credential read per connect, never captured. A token captured at module load survives a silent renewal as a dead credential, and a reconnect then signs its 22242 with it.
  • The network coming back. Reconnection is driven by onclose, and a socket whose peer became unreachable stays OPEN until TCP gives up. Without an online/offline signal it reports live, delivers nothing, and never retries.

Usage

import {
  browserOnlineSource,
  createLiveClientHolder,
} from '@estiva-app/platform'

// The app holds the one module-scope binding. This package holds none — that
// is the constraint-2 failure it exists to stop repeating.
const holder = createLiveClientHolder()

export function liveClient() {
  return holder.get({
    relayUrl: RELAY_URL,
    // Read fresh on every connect attempt.
    getCredential: () => {
      const token = validToken()
      return token && { accessToken: token.accessToken, pubkey: token.pubkey }
    },
    sign: signViaEstivaId,
    online: browserOnlineSource(window),
    subscriptionPrefix: 'my-app-',
    // Told what happened, never asked what to do — your freshness model stays
    // yours.
    observe: { onState: recordRelayState, onOnline: recordOnline },
  })
}

Watching one channel, or a whole workspace

Two paths, and the difference is a cost rather than a preference.

// Per channel, refcounted: one REQ each, every consumer sees every event.
// What a message projection wants.
client.subscriptions.subscribe(channelUuid, (event) => project(event))

// A whole workspace in one REQ per 128 Folders. You are told which Folder
// changed, never what changed.
client.watchFolders(folderIds, (folder) => refresh(folder), { kinds: [9, 1111] })

REQ is a client→server frame, so it is metered on the relay's LimitType::WsEvents. Measured against production: 50 unpaced REQs are accepted and the 51st is refused rate-limited: quota exceeded. So a 34-Folder workspace on the per-channel path spends 34 frames on every connect and every reconnect; watchFolders spends one.

Holding subscriptions, by contrast, costs nothing measurable — the probe round trip was 44.2 ms whether 1, 34 or 100 were open.

One thing to know before relying on watchFolders

The wide filter is measured, not assumed: one filter carrying 34 #h delivered 34 of 34 live against wss://estiva.estiva.app.

It is also a property of the deployed relay branch. nfb-demo-kinds carries a plural extract_channel_ids_from_filters that returns the whole channel set; on origin/main's singular version the same filter registers as a global subscription, and fan_out_scoped branches on the event's channel_id and never consults it for anything carrying an h. That build delivers 0 of 34, with no error at all — EOSE, live, silence.

So anything depending on this should be able to answer "did an event I did not publish actually arrive?", because nothing else distinguishes the two cases. Pass kinds while you are at it: a kindless wide filter was refused outright on that other build with restricted: p-gated events require #p matching your pubkey.

Refreshing, and waiting when the relay says so

Buzz meters POST /query at 300 a minute per pubkey, so every tab and app signed in as one person shares one allowance. Build one budget and one scheduler per tab:

import { createRefreshScheduler, createRelayBudget } from '@estiva-app/platform'

// The transport writes it; the cadence reads it.
export const relayBudget = createRelayBudget({
  // Optional: the app's other tabs learn a pause from this one.
  channel: new BroadcastChannel('my-app-relay-budget'),
})
export const refreshScheduler = createRefreshScheduler({ document, window, budget: relayBudget })

// In the transport, on every answer:
if (status === 429 || isRateLimited(body)) relayBudget.noteRateLimited(body)

// In a hook: focus, becoming visible, and every 30 s while visible. A tab
// switch raises two events and costs one read; nothing runs while paused.
useEffect(() => refreshScheduler.subscribe(reload, { interval: !live }), [live])

// Before retrying a refused read: every waiter goes after the longest pause
// anybody learned, not its own shorter one.
await relayBudget.whenClear()

Nothing here gates a read. A read somebody asked for by clicking still goes out at once; only the cadence backs off.

What this deliberately does not know

Where anything is stored. Peek's version took a LiveProjectionApi, and extracting that as-is would have baked a Convex assumption into a package two apps depend on. This delivers frames and connection state and nothing else, so it bets on neither outcome of "do we need Convex": if Convex stays nothing here changes, and if it goes this is already right.

Your freshness states and their copy are product voice and stay in your app. observe is how they get what they need.

License

MIT