@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.
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-relayOr with npm:
npm install @nostrwatch/memory-relayQuick 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]}]))
// 1API
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)— returnfalseto reject an event before insert;trueto acceptinstantiate(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 connectionapps/gui— SvelteKit app that usesSvelteMemoryRelayfor local event caching and reactive UI updates
