API Reference
Full reference for all classes, functions, and types in tsunagiya v0.3.0.
Table of Contents
Main Module
typescript
import {
AuthState,
classifyEvent,
createLogger,
filterEvents,
generateChallenge,
getParameterizedId,
isEphemeral,
isParameterizedReplaceable,
isReplaceable,
Logger,
matchFilter,
matchFilters,
MockPool,
MockRelay,
} from "@ikuradon/tsunagiya";MockPool constructor:
typescript
new MockPool();relay(url, options) method:
typescript
const relay = pool.relay("wss://relay.example.com");
// with options
const relay = pool.relay("wss://relay.example.com", {
latency: 100,
errorRate: 0.1,
});install() / uninstall():
typescript
pool.install();
// ...
pool.uninstall();reset():
typescript
pool.reset();connections:
typescript
console.log(pool.connections); // Map { "wss://relay.example.com" => 2 }store(event):
typescript
relay.store({
id: "abc123",
pubkey: "pubkey1",
kind: 1,
content: "hello",
created_at: 1700000000,
tags: [],
sig: "sig1",
}); // → true
// Ephemeral events are not added to the store
relay.store(EventBuilder.kind(20001).build()); // → falseonREQ(handler):
typescript
relay.onREQ((subId, filters) => {
return [customEvent];
});
// async is also supported
relay.onREQ(async (subId, filters) => {
return await fetchEvents(filters);
});onEVENT(handler):
typescript
relay.onEVENT((event) => {
return ["OK", event.id, true, ""];
});
// to reject
relay.onEVENT((event) => {
return ["OK", event.id, false, "blocked: spam"];
});onCOUNT(handler):
typescript
relay.onCOUNT((subId, filters) => {
return { count: 42 };
});Error cases:
typescript
relay.refuse();
relay.disconnect(); // code: 1000
relay.disconnect(1006); // abnormal disconnect
relay.disconnectAfter(3000); // disconnect after 3 seconds
relay.close(1006);
relay.sendRaw("not json");
relay.sendNotice("rate-limited");NIP-42 AUTH:
typescript
// Default validation (no validator set): kind:22242 + challenge + relay URL match
// Custom validator: replaces relay URL check
relay.requireAuth((authEvent, context) => {
// context.relayUrl, context.challenge are available
return authEvent.tags.some(
(t) => t[0] === "relay" && t[1] === context.relayUrl,
);
});Verification helpers:
typescript
const req = relay.findREQ("sub1");
const count = relay.findCOUNT("count1");
const snap = relay.snapshot();
relay.restore(snap);Filter functions:
typescript
const matches = matchFilter(event, { kinds: [1], authors: ["pubkey1"] });
const matches = matchFilters(event, [
{ kinds: [1] },
{ kinds: [0], authors: ["pubkey1"] },
]);
const results = filterEvents(allEvents, { kinds: [1], limit: 10 });Event type functions:
typescript
classifyEvent(1); // "regular"
classifyEvent(10002); // "replaceable"
classifyEvent(20001); // "ephemeral"
classifyEvent(30023); // "parameterized_replaceable"
const event = EventBuilder.kind(30023)
.tag("d", "my-article")
.pubkey("author-pubkey")
.build();
getParameterizedId(event); // "30023:author-pubkey:my-article"
getParameterizedId(EventBuilder.kind1().build()); // nullMockPool Method Reference
| Method/Property | Description |
|---|---|
relay(url, options?): MockRelay | Register and retrieve a MockRelay |
install(): void | Replace globalThis.WebSocket with MockWebSocket |
uninstall(): void | Restore the original WebSocket |
reset(): void | Reset the state of all relays |
connections: Map<string, number> (readonly) | Current active connections |
installed: boolean (readonly) | Whether install has been called |
[Symbol.dispose](): void | For using syntax. Calls uninstall() if installed |
[Symbol.asyncDispose](): Promise<void> | For await using syntax. Same as above |
MockRelay Property Reference
| Property | Type | Description |
|---|---|---|
url | string (readonly) | Relay URL |
options | MockRelayOptions (readonly) | Relay options |
received | ClientMessage[] (readonly) | All received messages |
connectionCount | number (readonly) | Number of active connections |
errors | ReadonlyArray<string> (readonly) | Log of error responses that occurred |
deletedIds | ReadonlySet<string> (readonly) | Deleted event IDs (NIP-09) |
logger | Logger | null (readonly) | Logger instance |
authResults | ReadonlyArray<{ eventId: string; accepted: boolean; message: string }> (readonly) | AUTH authentication result log |
Subscription Management
| Method | Return Type | Description |
|---|---|---|
getSubscriptions() | ReadonlyMap<string, ReadonlyArray<NostrFilter>> | List of active subscriptions |
clearOlderThan(timestamp: number) | number | Delete events older than the specified timestamp |
broadcast(event: NostrEvent) | void | Deliver event to active subscriptions |
NIP-11 Relay Information
| Method | Signature | Description |
|---|---|---|
setInfo | setInfo(info: Partial<RelayInformation>): void | Set relay information |
getInfo | getInfo(): RelayInformation | Get relay information |
Testing Module
typescript
import {
assertAuthCompleted,
assertClosed,
assertEventPublished,
assertNoErrors,
assertReceived,
assertReceivedREQ,
EventBuilder,
FilterBuilder,
restore,
snapshot,
startStream,
streamEvents,
} from "@ikuradon/tsunagiya/testing";EventBuilder static helpers:
typescript
const events = EventBuilder.bulk(100, { kind: 1 });
const events = EventBuilder.timeline(50, {
kind: 1,
interval: 60,
startTime: 1700000000,
});
const thread = EventBuilder.thread(5);
// thread[0]: root, thread[1]: reply1, ...
const [post, reactions] = EventBuilder.withReactions(5);EventBuilder extended helpers:
ts
// clone and modify existing event
const original = EventBuilder.kind1().content("hello").build();
const modified = EventBuilder.from(original).content("world").build();
// auto-generate event matching a filter
const filter = { kinds: [1], authors: ["abc123"] };
const event = EventBuilder.matchFilter(filter);
// NIP-17 private DM in one call
const dm = EventBuilder.privateDM({
recipientPubkey: "recipient-pubkey",
content: "secret message",
});
// NIP-40 expiring event
const expiring = EventBuilder.kind1()
.content("temporary")
.withExpiration(Math.floor(Date.now() / 1000) + 3600)
.build();
// deterministic bulk generation with seed
const events = EventBuilder.bulk(5, { seed: "test-seed" });NIP-09 deletion request:
typescript
const deletion = EventBuilder.deletion(["event-id-1", "event-id-2"])
.pubkey(authorPubkey)
.build();
// → kind: 5, tags: [["e", "event-id-1"], ["e", "event-id-2"]]
const deletion = EventBuilder.deletionByAddress([
"30023:pubkey:article-slug",
])
.pubkey(authorPubkey)
.build();
// → kind: 5, tags: [["a", "30023:pubkey:article-slug"]]NIP-specific templates:
typescript
EventBuilder.metadata({ name: "Alice", about: "Nostr user", picture: "..." });
EventBuilder.contacts(["pubkey1", "pubkey2"]);
EventBuilder.dm("recipient-pubkey", "message").build();
EventBuilder.groupMessage("group-id").content("hello group").build();
const zap = EventBuilder.zapRequest({
amount: 1000,
relays: ["wss://relay.example.com"],
lnurl: "lnurl1...",
eventId: "target-event",
recipientPubkey: "recipient-pub",
});
// → kind: 9734
const event = EventBuilder.nip07Request();
// → kind: 24133, content: "mock-nip07-request"CorruptOptions:
typescript
const broken = EventBuilder.kind1()
.corrupt({
id: true, // set id to invalid value
pubkey: true, // set pubkey to invalid value
sig: true, // set signature to invalid value
created_at: true, // set created_at to -1
})
.build();FilterBuilder:
typescript
// generic filters
const authorFilter = FilterBuilder.author("pubkey123");
const kindFilter = FilterBuilder.kind(7);
const sinceFilter = FilterBuilder.since(1700000000);
const tagFilter = FilterBuilder.tagged("e", ["event1"]);
// combine filters
const combined = FilterBuilder.combine(
{ kinds: [1], since: 100 },
{ kinds: [7], since: 200 },
);
// → { kinds: [1, 7], since: 200 }
FilterBuilder.timeline({ limit: 20 });
// → { kinds: [1], limit: 20 }
FilterBuilder.profile("pubkey1");
// → { kinds: [0], authors: ["pubkey1"] }
FilterBuilder.mentions("pubkey1");
// → { kinds: [1], "#p": ["pubkey1"] }
FilterBuilder.reactions("event1");
// → { kinds: [7], "#e": ["event1"] }
FilterBuilder.search("nostr");
// → { search: "nostr" }
// NIP-17: Private Direct Messages
FilterBuilder.giftWraps("recipient-pubkey");
// → { kinds: [1059], "#p": ["recipient-pubkey"] }
FilterBuilder.dmRelayList("pubkey1");
// → { kinds: [10050], authors: ["pubkey1"] }
// NIP-18: Reposts
FilterBuilder.reposts("event-id");
// → { kinds: [6], "#e": ["event-id"] }
FilterBuilder.allReposts("event-id");
// → { kinds: [6, 16], "#e": ["event-id"] }
// NIP-23: Long-form Content
FilterBuilder.longFormContent("pubkey1");
// → { kinds: [30023], authors: ["pubkey1"] }
FilterBuilder.longFormByTag("nostr");
// → { kinds: [30023], "#t": ["nostr"] }
// NIP-25: Reactions (address-based)
FilterBuilder.reactionsTo("30023:pubkey1:article-slug");
// → { kinds: [7], "#a": ["30023:pubkey1:article-slug"] }
// NIP-51: Lists
FilterBuilder.muteList("pubkey1");
// → { kinds: [10000], authors: ["pubkey1"] }
FilterBuilder.pinList("pubkey1");
// → { kinds: [10001], authors: ["pubkey1"] }
FilterBuilder.bookmarks("pubkey1");
// → { kinds: [10003], authors: ["pubkey1"] }
FilterBuilder.followSets("pubkey1");
// → { kinds: [30000], authors: ["pubkey1"] }
// NIP-52: Calendar Events
FilterBuilder.calendarDateEvents();
// → { kinds: [31922] }
FilterBuilder.calendarTimeEvents();
// → { kinds: [31923] }
FilterBuilder.calendarEvents();
// → { kinds: [31922, 31923] }
FilterBuilder.calendarCollections();
// → { kinds: [31924] }
FilterBuilder.rsvps("31922:pubkey1:event-slug");
// → { kinds: [31925], "#a": ["31922:pubkey1:event-slug"] }
// NIP-65: Relay List Metadata
FilterBuilder.relayList("pubkey1");
// → { kinds: [10002], authors: ["pubkey1"] }Assertion functions:
typescript
assertReceivedREQ(relay, { kinds: [1] });
assertEventPublished(relay, "event-id");
assertNoErrors(relay);
assertAuthCompleted(relay);
assertClosed(relay, "sub1");
assertReceived(relay, (messages) => messages.some((m) => m[0] === "REQ"));Stream functions:
typescript
const handle = streamEvents(relay, events, {
interval: 100, // send interval (ms)
jitter: 50, // jitter range (±ms)
});
handle.stop();
const stream = startStream(relay, {
eventGenerator: () => EventBuilder.random({ kind: 1 }),
interval: 1000,
count: 10,
});
stream.stop();Snapshot functions:
typescript
const snap = snapshot(relay);
// ... operations ...
restore(relay, snap);EventBuilder Factory Methods
| Method | Description |
|---|---|
EventBuilder.kind0() | kind:0 (Metadata) builder |
EventBuilder.kind1() | kind:1 (Short Text Note) builder |
EventBuilder.kind3() | kind:3 (Contacts) builder |
EventBuilder.kind4() | kind:4 (Encrypted DM) builder |
EventBuilder.kind7() | kind:7 (Reaction) builder |
EventBuilder.kind(k: number) | Any kind |
EventBuilder.from(event: NostrEvent) | Restore builder from existing event |
EventBuilder.matchFilter(filter: NostrFilter) | Auto-generate event matching a filter |
EventBuilder.privateDM(options: ChatMessageOptions) | NIP-17 private DM in one call (kind:1059) |
EventBuilder Builder Methods
All builder methods return EventBuilder (chainable).
| Method | Description |
|---|---|
content(text: string) | Set content |
tag(key: string, ...values: string[]) | Add tag |
pubkey(pubkey: string) | Set public key |
id(id: string) | Set ID |
createdAt(timestamp: number) | Set created_at |
sign(privateKey?: string) | Generate mock signature (not cryptographically valid) |
corrupt(options: CorruptOptions) | Replace fields with invalid values |
geohash(hash: string) | Add geohash tag (NIP-52) |
emoji(name: string, url: string) | Add emoji tag (NIP-30) |
withExpiration(timestamp: number) | Add NIP-40 expiration tag |
build() | Build and return NostrEvent |
Logger
| Method/Property | Type | Description |
|---|---|---|
level | LogLevel (readonly) | Current log level |
entries | ReadonlyArray<LogEntry> (readonly) | Accumulated log entries |
setLevel(level) | void | Change log level |
setHandler(handler) | void | Set custom handler |
clear() | void | Clear log entries |
log(entry, level?) | void | Record a log entry |
Type Definitions
NostrEvent:
typescript
interface NostrEvent {
id: string; // event ID (64-char hex)
pubkey: string; // public key (64-char hex)
created_at: number; // UNIX timestamp (seconds)
kind: number; // event kind
tags: string[][]; // tag array
content: string; // content string
sig: string; // signature (128-char hex)
}NostrFilter:
typescript
interface NostrFilter {
ids?: string[]; // ID prefix match
authors?: string[]; // public key prefix match
kinds?: number[]; // exact kind match
since?: number; // created_at lower bound (inclusive)
until?: number; // created_at upper bound (inclusive)
limit?: number; // maximum results
search?: string; // NIP-50: search keyword
[key: `#${string}`]: string[] | undefined; // tag filter
}ClientMessage:
typescript
type ClientMessage =
| ["EVENT", NostrEvent]
| ["REQ", string, ...NostrFilter[]]
| ["CLOSE", string]
| ["AUTH", NostrEvent]
| ["COUNT", string, ...NostrFilter[]];RelayMessage:
typescript
type RelayMessage =
| ["EVENT", string, NostrEvent]
| ["OK", string, boolean, string]
| ["EOSE", string]
| ["CLOSED", string, string]
| ["NOTICE", string]
| ["AUTH", string]
| ["COUNT", string, { count: number }];MockRelayOptions:
typescript
interface MockRelayOptions {
latency?: { min: number; max: number } | number;
errorRate?: number; // 0.0 - 1.0
disconnectRate?: number; // 0.0 - 1.0
connectionTimeout?: number; // ms
connectionDelay?: number; // ms (simulate connection delay)
requiresAuth?: boolean;
logging?: boolean | LogHandler;
verifier?: EventVerifier; // event signature verification
}LogEntry:
typescript
interface LogEntry {
timestamp: number; // ms
relay: string; // relay URL
direction: "send" | "receive";
data: unknown;
}RelaySnapshot:
typescript
interface RelaySnapshot {
timestamp: number; // save time (ms)
store: NostrEvent[]; // events in store
received: ClientMessage[]; // received message log
deletedIds?: string[]; // deleted event IDs (NIP-09)
info?: RelayInformation; // relay information (NIP-11)
metadata?: {
subscriptionCount: number; // number of subscriptions
connectionCount: number; // number of connections
eventCount: number; // number of events
};
}EventKind:
typescript
type EventKind =
| "regular"
| "replaceable"
| "ephemeral"
| "parameterized_replaceable";COUNTHandler:
typescript
type COUNTHandler = (
subId: string,
filters: NostrFilter[],
) => { count: number } | Promise<{ count: number }>;LogLevel:
typescript
type LogLevel = "silent" | "error" | "info" | "debug" | "trace";UnsignedEvent:
typescript
interface UnsignedEvent {
pubkey: string; // public key (64-char hex)
created_at: number; // UNIX timestamp (seconds)
kind: number; // event kind
tags: string[][]; // tag array
content: string; // content string
}EventSigner:
typescript
interface EventSigner {
getPublicKey(): string | Promise<string>;
signEvent(
event: UnsignedEvent,
): { id: string; sig: string } | Promise<{ id: string; sig: string }>;
}EventVerifier:
typescript
interface EventVerifier {
verifyEvent(event: NostrEvent): boolean | Promise<boolean>;
}Related Documentation
- Tutorial — Step-by-step guide
- Examples — Practical usage examples
- NIP Support Status — Per-NIP support details