API リファレンス
繋ぎ屋 v0.3.0 の全クラス・関数・型の詳細リファレンスです。
目次
メインモジュール
typescript
import {
AuthState,
classifyEvent,
createLogger,
filterEvents,
generateChallenge,
getParameterizedId,
isEphemeral,
isParameterizedReplaceable,
isReplaceable,
Logger,
matchFilter,
matchFilters,
MockPool,
MockRelay,
} from "@ikuradon/tsunagiya";MockPool コンストラクタ:
typescript
new MockPool();relay(url, options) メソッド:
typescript
const relay = pool.relay("wss://relay.example.com");
// オプション付き
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 イベントはストアに追加されない
relay.store(EventBuilder.kind(20001).build()); // → falseonREQ(handler):
typescript
relay.onREQ((subId, filters) => {
return [customEvent];
});
// 非同期も可能
relay.onREQ(async (subId, filters) => {
return await fetchEvents(filters);
});onEVENT(handler):
typescript
relay.onEVENT((event) => {
return ["OK", event.id, true, ""];
});
// 拒否する場合
relay.onEVENT((event) => {
return ["OK", event.id, false, "blocked: spam"];
});onCOUNT(handler):
typescript
relay.onCOUNT((subId, filters) => {
return { count: 42 };
});エラーケース:
typescript
relay.refuse();
relay.disconnect(); // code: 1000
relay.disconnect(1006); // 異常切断
relay.disconnectAfter(3000); // 3秒後に切断
relay.close(1006);
relay.sendRaw("not json");
relay.sendNotice("rate-limited");NIP-42 AUTH:
typescript
// 標準検証(バリデーター未設定): kind:22242 + challenge + relay URL 一致
// カスタムバリデーター: relay URL チェックを置き換える
relay.requireAuth((authEvent, context) => {
// context.relayUrl, context.challenge が参照可能
return authEvent.tags.some(
(t) => t[0] === "relay" && t[1] === context.relayUrl,
);
});検証ヘルパー:
typescript
const req = relay.findREQ("sub1");
const count = relay.findCOUNT("count1");
const snap = relay.snapshot();
relay.restore(snap);フィルター関数:
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 });イベント種別関数:
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 メソッド一覧
| メソッド/プロパティ | 説明 |
|---|---|
relay(url, options?): MockRelay | MockRelay を登録・取得する |
install(): void | globalThis.WebSocket を MockWebSocket に差し替える |
uninstall(): void | 元の WebSocket を復元する |
reset(): void | 全リレーの状態をリセットする |
connections: Map<string, number> (readonly) | 現在のアクティブ接続一覧 |
installed: boolean (readonly) | install 済みかどうか |
[Symbol.dispose](): void | using 構文用。install 済みなら uninstall() を呼ぶ |
[Symbol.asyncDispose](): Promise<void> | await using 構文用。同上 |
MockRelay プロパティ一覧
| プロパティ | 型 | 説明 |
|---|---|---|
url | string (readonly) | リレーURL |
options | MockRelayOptions (readonly) | リレーオプション |
received | ClientMessage[] (readonly) | 全受信メッセージ |
connectionCount | number (readonly) | アクティブ接続数 |
errors | ReadonlyArray<string> (readonly) | 発生したエラーレスポンスのログ |
deletedIds | ReadonlySet<string> (readonly) | 削除済みイベントID (NIP-09) |
logger | Logger | null (readonly) | ロガーインスタンス |
authResults | ReadonlyArray<{ eventId: string; accepted: boolean; message: string }> (readonly) | AUTH認証結果のログ |
サブスクリプション管理
| メソッド | 戻り値 | 説明 |
|---|---|---|
getSubscriptions() | ReadonlyMap<string, ReadonlyArray<NostrFilter>> | アクティブなサブスクリプション一覧 |
clearOlderThan(timestamp: number) | number | 指定タイムスタンプより古いイベントを削除 |
broadcast(event: NostrEvent) | void | イベントをアクティブなサブスクリプションに配信 |
NIP-11 リレー情報
| メソッド | シグネチャ | 説明 |
|---|---|---|
setInfo | setInfo(info: Partial<RelayInformation>): void | リレー情報を設定 |
getInfo | getInfo(): RelayInformation | リレー情報を取得 |
テストモジュール
typescript
import {
assertAuthCompleted,
assertClosed,
assertEventPublished,
assertNoErrors,
assertReceived,
assertReceivedREQ,
EventBuilder,
FilterBuilder,
restore,
snapshot,
startStream,
streamEvents,
} from "@ikuradon/tsunagiya/testing";EventBuilder スタティックヘルパー:
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 拡張ヘルパー:
ts
// 既存イベントの複製・修正
const original = EventBuilder.kind1().content("hello").build();
const modified = EventBuilder.from(original).content("world").build();
// フィルターにマッチするイベントを自動生成
const filter = { kinds: [1], authors: ["abc123"] };
const event = EventBuilder.matchFilter(filter);
// NIP-17 プライベートDM一括生成
const dm = EventBuilder.privateDM({
recipientPubkey: "recipient-pubkey",
content: "secret message",
});
// NIP-40 有効期限付きイベント
const expiring = EventBuilder.kind1()
.content("temporary")
.withExpiration(Math.floor(Date.now() / 1000) + 3600)
.build();
// シード指定で決定論的バルク生成
const events = EventBuilder.bulk(5, { seed: "test-seed" });NIP-09 削除リクエスト:
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 別テンプレート:
typescript
EventBuilder.metadata({ name: "Alice", about: "Nostr user", picture: "..." });
EventBuilder.contacts(["pubkey1", "pubkey2"]);
EventBuilder.dm("recipient-pubkey", "メッセージ").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, // IDを不正な値にする
pubkey: true, // pubkeyを不正な値にする
sig: true, // 署名を不正な値にする
created_at: true, // created_atを-1にする
})
.build();FilterBuilder:
typescript
// 汎用フィルター
const authorFilter = FilterBuilder.author("pubkey123");
const kindFilter = FilterBuilder.kind(7);
const sinceFilter = FilterBuilder.since(1700000000);
const tagFilter = FilterBuilder.tagged("e", ["event1"]);
// フィルターの結合
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 (アドレス指定)
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"] }アサーション関数:
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"));ストリーム関数:
typescript
const handle = streamEvents(relay, events, {
interval: 100, // 送信間隔 (ms)
jitter: 50, // ジッター幅 (±ms)
});
handle.stop();
const stream = startStream(relay, {
eventGenerator: () => EventBuilder.random({ kind: 1 }),
interval: 1000,
count: 10,
});
stream.stop();スナップショット関数:
typescript
const snap = snapshot(relay);
// ... 操作 ...
restore(relay, snap);EventBuilder ファクトリメソッド
| メソッド | 説明 |
|---|---|
EventBuilder.kind0() | kind:0 (Metadata) ビルダー |
EventBuilder.kind1() | kind:1 (Short Text Note) ビルダー |
EventBuilder.kind3() | kind:3 (Contacts) ビルダー |
EventBuilder.kind4() | kind:4 (Encrypted DM) ビルダー |
EventBuilder.kind7() | kind:7 (Reaction) ビルダー |
EventBuilder.kind(k: number) | 任意の kind |
EventBuilder.from(event: NostrEvent) | 既存イベントからビルダーを復元 |
EventBuilder.matchFilter(filter: NostrFilter) | フィルターにマッチするイベントを自動生成 |
EventBuilder.privateDM(options: ChatMessageOptions) | NIP-17 プライベートDM一括生成(kind:1059) |
EventBuilder ビルダーメソッド
すべてのビルダーメソッドは EventBuilder を返す(チェーン可能)。
| メソッド | 説明 |
|---|---|
content(text: string) | コンテンツ設定 |
tag(key: string, ...values: string[]) | タグ追加 |
pubkey(pubkey: string) | 公開鍵設定 |
id(id: string) | ID 設定 |
createdAt(timestamp: number) | created_at 設定 |
sign(privateKey?: string) | モック署名生成(暗号的に正しくない) |
corrupt(options: CorruptOptions) | フィールドを不正な値に置換 |
geohash(hash: string) | geohash タグ追加 (NIP-52) |
emoji(name: string, url: string) | emoji タグ追加 (NIP-30) |
withExpiration(timestamp: number) | NIP-40 有効期限タグを追加 |
build() | NostrEvent を構築して返す |
Logger
| メソッド/プロパティ | 型 | 説明 |
|---|---|---|
level | LogLevel (readonly) | 現在のログレベル |
entries | ReadonlyArray<LogEntry> (readonly) | 蓄積されたログエントリ |
setLevel(level) | void | ログレベル変更 |
setHandler(handler) | void | カスタムハンドラー設定 |
clear() | void | ログエントリクリア |
log(entry, level?) | void | ログ記録 |
型定義
NostrEvent:
typescript
interface NostrEvent {
id: string; // イベントID (64文字hex)
pubkey: string; // 公開鍵 (64文字hex)
created_at: number; // UNIXタイムスタンプ (秒)
kind: number; // イベント種別
tags: string[][]; // タグ配列
content: string; // コンテンツ文字列
sig: string; // 署名 (128文字hex)
}NostrFilter:
typescript
interface NostrFilter {
ids?: string[]; // IDプレフィックスマッチ
authors?: string[]; // 公開鍵プレフィックスマッチ
kinds?: number[]; // kind完全一致
since?: number; // created_at下限 (inclusive)
until?: number; // created_at上限 (inclusive)
limit?: number; // 返却数上限
search?: string; // NIP-50: 検索キーワード
[key: `#${string}`]: string[] | undefined; // タグフィルター
}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(接続遅延シミュレート)
requiresAuth?: boolean;
logging?: boolean | LogHandler;
verifier?: EventVerifier; // イベント署名検証
}LogEntry:
typescript
interface LogEntry {
timestamp: number; // ms
relay: string; // リレーURL
direction: "send" | "receive";
data: unknown;
}RelaySnapshot:
typescript
interface RelaySnapshot {
timestamp: number; // 保存時刻 (ms)
store: NostrEvent[]; // ストア内のイベント
received: ClientMessage[]; // 受信メッセージログ
deletedIds?: string[]; // 削除済みイベントID (NIP-09)
info?: RelayInformation; // リレー情報 (NIP-11)
metadata?: {
subscriptionCount: number; // サブスクリプション数
connectionCount: number; // 接続数
eventCount: number; // イベント数
};
}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; // 公開鍵 (64文字hex)
created_at: number; // UNIXタイムスタンプ (秒)
kind: number; // イベント種別
tags: 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>;
}