Skip to content

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()); // → false

onREQ(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()); // null

MockPool メソッド一覧

メソッド/プロパティ説明
relay(url, options?): MockRelayMockRelay を登録・取得する
install(): voidglobalThis.WebSocket を MockWebSocket に差し替える
uninstall(): void元の WebSocket を復元する
reset(): void全リレーの状態をリセットする
connections: Map<string, number> (readonly)現在のアクティブ接続一覧
installed: boolean (readonly)install 済みかどうか
[Symbol.dispose](): voidusing 構文用。install 済みなら uninstall() を呼ぶ
[Symbol.asyncDispose](): Promise<void>await using 構文用。同上

MockRelay プロパティ一覧

プロパティ説明
urlstring (readonly)リレーURL
optionsMockRelayOptions (readonly)リレーオプション
receivedClientMessage[] (readonly)全受信メッセージ
connectionCountnumber (readonly)アクティブ接続数
errorsReadonlyArray<string> (readonly)発生したエラーレスポンスのログ
deletedIdsReadonlySet<string> (readonly)削除済みイベントID (NIP-09)
loggerLogger | null (readonly)ロガーインスタンス
authResultsReadonlyArray<{ eventId: string; accepted: boolean; message: string }> (readonly)AUTH認証結果のログ

サブスクリプション管理

メソッド戻り値説明
getSubscriptions()ReadonlyMap<string, ReadonlyArray<NostrFilter>>アクティブなサブスクリプション一覧
clearOlderThan(timestamp: number)number指定タイムスタンプより古いイベントを削除
broadcast(event: NostrEvent)voidイベントをアクティブなサブスクリプションに配信

NIP-11 リレー情報

メソッドシグネチャ説明
setInfosetInfo(info: Partial<RelayInformation>): voidリレー情報を設定
getInfogetInfo(): 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

メソッド/プロパティ説明
levelLogLevel (readonly)現在のログレベル
entriesReadonlyArray<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>;
}

関連ドキュメント

MIT License