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

@nostrwatch/memory-relay

v2.0.0

Published

A simple memory relay

Downloads

25

Readme

@nostrwatch/memory-relay

In-memory Nostr relay implementation for testing and SvelteKit application state.

npm version License Status Runtime

Overview

@nostrwatch/memory-relay provides an in-memory store for Nostr events (signed JSON objects that form the protocol's fundamental data unit) without any network layer or disk persistence. It is used in tests to simulate relay behavior and in SvelteKit applications to hold reactive event collections without a backend relay connection.

The package exports two classes: AbstractMemoryRelay — a generic base class with filter-aware query, insert, count, and delete operations — and SvelteMemoryRelay, which extends AbstractMemoryRelay to back all event storage with a Svelte Writable store, making the event collection reactive and compatible with Svelte's $ syntax and derived stores.

A relay in Nostr is a WebSocket server that stores and forwards events. This package emulates the data model of a relay in memory without the network transport.

Installation

pnpm add @nostrwatch/memory-relay

Or with npm:

npm install @nostrwatch/memory-relay

Quick Start

import {AbstractMemoryRelay} from '@nostrwatch/memory-relay'
import type {Filter} from 'nostr-tools'

class MyRelay extends AbstractMemoryRelay {
  async init(): Promise<void> {}
}

const relay = new MyRelay()

// Insert a Nostr event
relay.event({id: 'abc123', kind: 1, created_at: 1700000000, content: 'hello', tags: [], pubkey: 'pub1'})

// Query with a filter
const results = relay.req('sub1', [{kinds: [1]}])
console.log(relay.count([{kinds: [1]}]))
// 1

API

AbstractMemoryRelay

abstract class AbstractMemoryRelay<
  Input extends BaseEvent = any,
  Output extends BaseEvent = any,
  OutputSingle = Output,
  OutputCollection = any,
  OutputCount = any
>

Abstract base class for in-memory event storage. Extend this class and implement init() to create a concrete relay. Subclasses may override formatCollection() to shape query results, and register qualify and instantiate callbacks to control which events are stored and how they are transformed on insert.

abstract init(): Promise<void>

Called once before the relay is used. Implement to perform any async setup (e.g. loading seed events from storage). Must return a resolved promise if no setup is needed.


Event writes

event(ev: Input): boolean

Inserts a single event. Returns true if the event was inserted, false if it was rejected (by the qualify callback or because a newer replaceable event already exists).

eventBatch(evs: Input[]): number

Inserts multiple events. Returns the count of events actually inserted.

maybeInsert(event: Input): number

Lower-level insert: checks shouldInsert(), then stores the event. Returns 1 if inserted, 0 otherwise.


Event reads

req(id: string, filters: Filter[], format?: boolean): OutputCollection | Output[]

Queries the store against one or more Nostr filters (a filter is a JSON object with fields like kinds, authors, since, until, #e, etc.). Returns all matching events. When format is true (default), passes results through formatCollection().

get(key: string): Output | undefined

Returns a single event by its internal storage key (event ID or kind:pubkey for replaceable events).

count(filters: Filter[]): OutputCount

Returns the number of events that match the given filters.

summary(): Record<string, number>

Returns a map of { kind: count } for all stored events. Useful for debugging and test assertions.


Event deletes

delete(filters: Filter[]): string[]

Deletes all events matching the filters. Returns the keys of deleted events.

wipe(): Promise<void>

Clears all stored events.

destroy(): void

Calls wipe() and releases resources.


Existence checks

exists(note: Input | string): boolean

Returns true if the given event (or event ID string) is present in the store.

noteExists(note: Input): boolean

Returns true if the given event object is in the store.

idExists(id: string): boolean

Returns true if the given event ID string is in the store.


Lifecycle callbacks

on(event: 'qualify' | 'instantiate', listener): void

Registers a callback for event lifecycle hooks:

  • qualify(event, key, relay) — return false to reject an event before insert; true to accept
  • instantiate(event, key, relay) — transform an input event into the stored output type; return the transformed event

off(event: 'qualify' | 'instantiate'): void

Removes the registered callback for the given lifecycle hook.


Serialization

dump(): Promise<Uint8Array>

Serializes all stored events to a UTF-8 JSON byte array. Useful for snapshotting relay state in tests.


Properties

| Property | Type | Description | |----------|------|-------------| | events | Map<string, Output> | Direct access to the event store | | nip11s | Map<string, any> | NIP-11 info document cache keyed by relay URL | | highestTimestamp | number[] | Per-filter highest created_at seen during req() | | lowestTimestamp | number[] | Per-filter lowest created_at seen during req() | | debounceMS | number | Debounce interval in milliseconds (default 1000) |


SvelteMemoryRelay

class SvelteMemoryRelay<
  InputEvent extends BaseEvent,
  OutputEvent extends BaseEvent,
  OutputSingle extends Readable<OutputEvent> = Readable<OutputEvent>,
  OutputCollection extends Readable<OutputEvent[]> = Readable<OutputEvent[]>,
  OutputCount extends Readable<number> = Readable<number>
> extends AbstractMemoryRelay<InputEvent, OutputEvent, OutputSingle, OutputCollection, OutputCount>

Extends AbstractMemoryRelay to back event storage with a Svelte Writable<Map<string, OutputEvent>> store. All mutations (maybeInsert, wipe, _delete) update the store atomically, triggering reactive updates in subscribing components.

Import from the svelte subpath:

import {SvelteMemoryRelay} from '@nostrwatch/memory-relay/svelte'
import {writable} from 'svelte/store'

Constructor

new SvelteMemoryRelay(store: Writable<Map<string, OutputEvent>>)

Pass an existing Writable store. The relay reads and writes through the store reference, so external subscribers see changes immediately.

Example (SvelteKit component):

import {SvelteMemoryRelay} from '@nostrwatch/memory-relay/svelte'
import {writable, derived} from 'svelte/store'

const eventStore = writable(new Map())
const relay = new SvelteMemoryRelay(eventStore)
await relay.init()

// Reactive query — updates whenever events are inserted or deleted
const kind1Events = relay.$req('sub1', [{kinds: [1]}])

// In Svelte template: {#each $kind1Events as event}

Reactive query methods

$req(id: string, filters: Filter[], format?: boolean): OutputCollection

Returns a Readable<OutputEvent[]> derived from the store. Updates reactively whenever the store changes.

$get(key: string): OutputSingle | undefined

Returns a Readable<OutputEvent> for a single event by key. Updates reactively.

$count(filters: Filter[]): OutputCount

Returns a Readable<number> that emits the count of matching events. Updates reactively.

Properties

store: Writable<Map<string, OutputEvent>>

Direct access to the underlying Svelte store. Subscribe to it to react to any event store change.


BaseEvent

interface BaseEvent {
  id?: string
  kind?: number
  created_at?: number
  tags?: string[][]
  pubkey?: string
  content?: string
  sig?: string
}

Minimum shape required by the relay internals. Use nostr-tools' Event type for full Nostr event compliance.

Known Limitations

No known limitations at this time.

Agent Skills

No agent skills defined yet for this package.

Related Packages

  • @nostrwatch/worker-relay — persistent web worker relay using sqlite-wasm and OPFS; use this when you need durability across page reloads
  • @nostrwatch/websocket — cross-platform WebSocket client; pairs with memory-relay in integration tests that simulate a real relay connection
  • apps/gui — SvelteKit app that uses SvelteMemoryRelay for local event caching and reactive UI updates

License

MIT