{"id":48129370,"url":"https://github.com/ikuradon/tsunagiya","last_synced_at":"2026-04-04T16:36:37.697Z","repository":{"id":338468301,"uuid":"1157994223","full_name":"ikuradon/tsunagiya","owner":"ikuradon","description":"繋ぎ屋 — Nostr relay mock library for testing. Intercepts WebSocket to test existing clients without code changes.","archived":false,"fork":false,"pushed_at":"2026-03-29T11:58:23.000Z","size":651,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-29T13:49:27.826Z","etag":null,"topics":["deno","jsr","mock","nostr","relay","testing","typescript","websocket"],"latest_commit_sha":null,"homepage":"https://ikuradon.github.io/tsunagiya/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ikuradon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-14T16:22:41.000Z","updated_at":"2026-03-29T11:57:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ikuradon/tsunagiya","commit_stats":null,"previous_names":["ikuradon/tsunagiya"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/ikuradon/tsunagiya","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ikuradon%2Ftsunagiya","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ikuradon%2Ftsunagiya/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ikuradon%2Ftsunagiya/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ikuradon%2Ftsunagiya/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ikuradon","download_url":"https://codeload.github.com/ikuradon/tsunagiya/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ikuradon%2Ftsunagiya/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31405705,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["deno","jsr","mock","nostr","relay","testing","typescript","websocket"],"created_at":"2026-04-04T16:36:35.328Z","updated_at":"2026-04-04T16:36:37.041Z","avatar_url":"https://github.com/ikuradon.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 繋ぎ屋 (tsunagiya)\n\nNostr relay mock library for Deno/TypeScript.\n\n`globalThis.WebSocket`\nを差し替えることで、**既存のNostrクライアントコードを一切変更せず**にテストできます。\n\n## インストール\n\n**Deno:**\n\n```bash\ndeno add jsr:@ikuradon/tsunagiya\n```\n\n**npm:**\n\n```bash\nnpm install @ikuradon/tsunagiya\n```\n\n## 基本的な使い方\n\n```typescript\nimport { MockPool } from \"@ikuradon/tsunagiya\";\n\nDeno.test(\"fetch events from relay\", async () =\u003e {\n  const pool = new MockPool();\n  const relay = pool.relay(\"wss://relay.example.com\");\n\n  relay.store({\n    id: \"abc123\",\n    pubkey: \"pubkey1\",\n    kind: 1,\n    content: \"hello nostr\",\n    created_at: 1700000000,\n    tags: [],\n    sig: \"sig1\",\n  });\n\n  pool.install();\n  try {\n    const ws = new WebSocket(\"wss://relay.example.com\");\n    // ... テスト対象のクライアントコードがそのまま動く\n  } finally {\n    pool.uninstall();\n  }\n});\n```\n\n## 機能\n\n- WebSocket 完全乗っ取り型モック\n- 複数リレー同時対応\n- Indexed EventStore + compiled filter fast path による高速な `REQ` / `COUNT`\n- NIP-01 フィルター自動マッチング + カスタムハンドラー\n- 不安定リレーのシミュレート（レイテンシ、エラー率、切断、ネットワーク条件シミュレーション）\n- NIP-42 AUTH チャレンジ/レスポンス\n- ランタイム入力検証（message/filter/subId/tag/content limit）\n- 送信メッセージの記録・検証ヘルパー\n- NIP-01 イベント種別自動処理（Regular/Replaceable/Ephemeral/Addressable）\n- NIP-09 Event Deletion Request\n- NIP-11 リレー情報ドキュメント（setInfo/getInfo + fetch インターセプト）\n- NIP-17 Private Direct Messages（EventBuilder\n  chatMessage/seal/giftWrap/dmRelayList/privateDM）\n- NIP-18 Reposts（EventBuilder repost/genericRepost）\n- NIP-23 Long-form Content（EventBuilder longFormContent/longFormDraft）\n- NIP-25 Reactions（EventBuilder withReactions/externalReaction）\n- NIP-40 Expiration Timestamp（EventBuilder `withExpiration()`）\n- NIP-51 Lists（EventBuilder\n  muteList/pinList/bookmarks/followSet/relaySet/emojiSet）\n- NIP-65 Relay List Metadata（EventBuilder relayList）\n- NIP-45 COUNT メッセージ対応\n- NIP-50 検索フィルター対応\n- テスト支援ヘルパー（EventBuilder, FilterBuilder, assertions）\n- リアルタイムストリーム・スナップショット\n- ログ機能（console / カスタムハンドラー）\n- テストフレームワーク非依存\n- 外部依存ゼロ\n- E2Eテスト対応（nostr-tools, NDK, rx-nostr, nostr-fetch）\n- 決定論的 runtime 注入（`MockRelayOptions.clock/random`,\n  `EventBuilderRuntimeOptions`, `StreamOptions.random`）\n\n## 公開 API 互換性と内部刷新\n\n2026-03 の全面リファクタリングでは、公開 API\nを維持したまま内部を再設計しました。\n\n- `MockPool`、`MockRelay`、`AuthState`、`@ikuradon/tsunagiya/testing` の import\n  path はそのまま使えます\n- 内部は `EventStore`、`SubscriptionRegistry`、`AuthService`、\n  `DeliveryScheduler`、platform hook に分割されています\n- 大きいストアに対する `REQ` / `COUNT` は、索引付きストアと compiled filter\n  により従来より高速です\n- 入力検証は厳格化されています。 `max_message_length`、filter\n  数、`max_subid_length`、`max_limit`、 `max_event_tags`、`max_content_length`\n  を routing 前に拒否します\n\n内部実装の import path は安定 API ではありません。公開 export を使ってください。\n\n2026-03 の全面リファクタリング本体は 2026-03-29 にクローズしました。\nクローズアウトの判断と以後の運用ルールは\n`docs/superpowers/plans/2026-03-29-refactor-closeout.md` と\n`docs/superpowers/plans/2026-03-29-maintainer-operating-rules.md`\nに集約しています。\n\n## MockPool\n\nテストのエントリポイント。複数の `MockRelay` を管理し、`globalThis.WebSocket`\nを差し替える。\n\n### 基本的な使い方\n\n```typescript\nconst pool = new MockPool();\nconst relay = pool.relay(\"wss://relay.example.com\");\n\npool.install(); // WebSocket差し替え\npool.uninstall(); // 元に戻す\npool.reset(); // 全リレーの状態をリセット\npool.connections; // アクティブ接続一覧 (Map\u003cstring, number\u003e)\n```\n\n### 複数リレーの使い方\n\n```typescript\nconst pool = new MockPool();\n\n// 複数のリレーを登録（それぞれ独立して動作）\nconst relay1 = pool.relay(\"wss://relay1.example.com\");\nconst relay2 = pool.relay(\"wss://relay2.example.com\");\nconst relay3 = pool.relay(\"wss://relay3.example.com\");\n\n// 各リレーに異なるイベントを登録\nrelay1.store(event1);\nrelay2.store(event2);\nrelay3.store(event3);\n\n// 各リレーに異なる設定も可能\nconst fastRelay = pool.relay(\"wss://fast.relay.test\", { latency: 10 });\nconst slowRelay = pool.relay(\"wss://slow.relay.test\", { latency: 500 });\n\npool.install();\ntry {\n  // 複数リレーに同時接続するクライアントコードがそのまま動く\n  const ws1 = new WebSocket(\"wss://relay1.example.com\");\n  const ws2 = new WebSocket(\"wss://relay2.example.com\");\n  const ws3 = new WebSocket(\"wss://relay3.example.com\");\n  // ... テスト対象のクライアントロジック\n} finally {\n  pool.uninstall();\n}\n```\n\n**注意:** `pool.relay()`\nで登録していないURLに接続しようとすると、接続失敗として扱われます（エラーイベント +\nクローズイベント\ncode:1006）。これは実際のリレーに接続できなかった場合と同じ動作です。\n\n## MockRelay\n\nURL単位で動作する仮想リレー。\n\n### イベントの登録とカスタムハンドラー\n\n```typescript\nconst relay = pool.relay(\"wss://relay.example.com\");\n\n// イベントを事前登録（REQ受信時に自動マッチング）\nrelay.store(event);\n\n// REQハンドラーのカスタマイズ\nrelay.onREQ((subId, filters) =\u003e {\n  return [customEvent];\n});\n\n// EVENTハンドラーのカスタマイズ\nrelay.onEVENT((event) =\u003e {\n  return [\"OK\", event.id, true, \"\"];\n});\n```\n\n### 不安定リレーのシミュレート\n\n```typescript\npool.relay(\"wss://unstable.relay.test\", {\n  latency: { min: 100, max: 2000 },\n  errorRate: 0.3,\n  disconnectRate: 0.1,\n  connectionTimeout: 5000,\n});\n```\n\n### エラーケーステスト\n\n```typescript\nrelay.refuse(); // 接続拒否\nrelay.disconnect(); // 全接続を即座に切断\nrelay.disconnectAfter(3000); // 3秒後に切断\nrelay.close(1006); // 特定クローズコードで切断\nrelay.sendRaw(\"not json\"); // 不正データ送信\nrelay.sendNotice(\"rate-limited\"); // NOTICE送信\n```\n\n### NIP-42 AUTH\n\n```typescript\nconst relay = pool.relay(\"wss://auth.relay.test\", {\n  requiresAuth: true,\n});\n\nrelay.requireAuth((authEvent) =\u003e {\n  return authEvent.tags.some(\n    (t) =\u003e t[0] === \"relay\" \u0026\u0026 t[1] === \"wss://auth.relay.test\",\n  );\n});\n```\n\n### 検証ヘルパー\n\n```typescript\nrelay.received; // 全受信メッセージ\nrelay.findREQ(\"sub1\"); // REQ検索\nrelay.countREQs(); // REQ数\nrelay.hasREQ(\"sub1\"); // REQ存在確認\nrelay.findEvent(\"id1\"); // EVENT検索\nrelay.countEvents(); // EVENT数\nrelay.hasEvent(\"id1\"); // EVENT存在確認\nrelay.findCLOSE(\"sub1\"); // CLOSE検索\nrelay.connectionCount; // アクティブ接続数\n```\n\n### NIP-01 イベント種別（旧 NIP-16/33）\n\n```typescript\nimport {\n  classifyEvent,\n  isEphemeral,\n  isParameterizedReplaceable,\n  isReplaceable,\n} from \"@ikuradon/tsunagiya\";\n\nclassifyEvent(10000); // \"replaceable\"\nclassifyEvent(20000); // \"ephemeral\"\nclassifyEvent(30000); // \"parameterized_replaceable\"\n```\n\n### NIP-09 削除リクエスト\n\n```typescript\nimport { EventBuilder } from \"@ikuradon/tsunagiya/testing\";\n\n// kind:5 削除リクエストイベントを生成\nconst deletion = EventBuilder.deletion([\"event-id-1\", \"event-id-2\"])\n  .content(\"spam\")\n  .build();\n\nrelay.store(deletion);\n// ストア内の対象イベントが自動的に削除される\n```\n\n### NIP-45 COUNT\n\n```typescript\n// COUNTハンドラーのカスタマイズ\nrelay.onCOUNT((subId, filters) =\u003e {\n  return { count: 42 };\n});\n\n// クライアントから COUNT メッセージを送信\nws.send(JSON.stringify([\"COUNT\", \"sub1\", { kinds: [1] }]));\n// =\u003e [\"COUNT\", \"sub1\", {\"count\": 42}]\n```\n\n### スナップショット\n\n```typescript\nconst snap = relay.snapshot();\n\nrelay.store(event2);\nrelay.store(event3);\n\nrelay.restore(snap); // event2, event3 追加前の状態に戻る\n```\n\n### ログ機能\n\n```typescript\n// console出力\npool.relay(\"wss://relay.example.com\", { logging: true });\n\n// カスタムハンドラー\nconst logs: LogEntry[] = [];\npool.relay(\"wss://relay.example.com\", {\n  logging: (entry) =\u003e logs.push(entry),\n});\n```\n\n## テスト支援ヘルパー\n\n`@ikuradon/tsunagiya/testing` からインポートする。\n\n```typescript\nimport {\n  assertReceivedREQ,\n  EventBuilder,\n  FilterBuilder,\n  restore,\n  snapshot,\n  startStream,\n  streamEvents,\n  waitFor,\n} from \"@ikuradon/tsunagiya/testing\";\n```\n\nEventBuilder の使用例:\n\n```typescript\n// ビルダーパターンでイベント生成\nconst event = EventBuilder.kind1()\n  .content(\"hello world\")\n  .tag(\"p\", pubkey)\n  .build();\n\n// ランダム生成\nconst random = EventBuilder.random({ kind: 1 });\n\n// deterministic runtime\nconst fixedClock = { now: () =\u003e 1700000000000 };\nconst fixedRandom = {\n  next: () =\u003e 0.25,\n  fill(bytes: Uint8Array) {\n    bytes.fill(0x11);\n  },\n};\nconst deterministic = EventBuilder.kind1({\n  clock: fixedClock,\n  random: fixedRandom,\n})\n  .content(\"stable\")\n  .build();\n\n// 壊れたイベント\nconst broken = EventBuilder.kind1()\n  .corrupt({ id: true, sig: true })\n  .build();\n\n// バルク生成\nconst events = EventBuilder.bulk(100, { kind: 1 });\n\n// 時系列データ\nconst timeline = EventBuilder.timeline(50, {\n  kind: 1,\n  interval: 60,\n  startTime: 1700000000,\n});\n\n// リプライチェーン\nconst thread = EventBuilder.thread(5);\n\n// リアクション付き\nconst [post, reactions] = EventBuilder.withReactions(3);\n\n// NIP別テンプレート\nEventBuilder.metadata({ name: \"Alice\", about: \"Nostr user\" });\nEventBuilder.contacts([\"pub1\", \"pub2\"]);\nEventBuilder.dm(\"recipient\", \"secret message\");\nEventBuilder.groupMessage(\"group-id\").content(\"hello\");\nEventBuilder.zapRequest({\n  amount: 1000,\n  relays: [\"wss://r.test\"],\n  lnurl: \"...\",\n});\n```\n\nFilterBuilder の使用例:\n\n```typescript\nFilterBuilder.timeline({ limit: 20 });\n// =\u003e { kinds: [1], limit: 20 }\n\nFilterBuilder.profile(\"pubkey\");\n// =\u003e { kinds: [0], authors: [\"pubkey\"] }\n\nFilterBuilder.mentions(\"pubkey\");\n// =\u003e { kinds: [1], \"#p\": [\"pubkey\"] }\n\nFilterBuilder.reactions(\"eventId\");\n// =\u003e { kinds: [7], \"#e\": [\"eventId\"] }\n\nFilterBuilder.search(\"nostr\");\n// =\u003e { search: \"nostr\" }\n```\n\nアサーションヘルパー:\n\n```typescript\nimport {\n  assertAuthCompleted,\n  assertClosed,\n  assertEventPublished,\n  assertNoErrors,\n  assertReceived,\n  assertReceivedREQ,\n} from \"@ikuradon/tsunagiya/testing\";\n\nassertReceivedREQ(relay, { kinds: [1] });\nassertEventPublished(relay, \"event-id\");\nassertNoErrors(relay);\nassertAuthCompleted(relay);\nassertClosed(relay, \"sub1\");\nassertReceived(relay, (messages) =\u003e messages.some((m) =\u003e m[0] === \"REQ\"));\n```\n\nリアルタイムストリーム:\n\n```typescript\nconst fixedRandom = {\n  next: () =\u003e 0.5,\n  fill(bytes: Uint8Array) {\n    bytes.fill(0x22);\n  },\n};\n\n// 時間差でイベント配信\nconst handle = streamEvents(relay, events, {\n  interval: 100,\n  jitter: 50,\n  random: fixedRandom,\n});\nhandle.stop();\n\n// 継続的ストリーム\nconst stream = startStream(relay, {\n  eventGenerator: () =\u003e EventBuilder.random({ kind: 1 }),\n  interval: 1000,\n  count: 10,\n  random: fixedRandom,\n});\nstream.stop();\n```\n\n条件待ちヘルパー:\n\n```typescript\nimport { waitFor } from \"@ikuradon/tsunagiya/testing\";\n\n// 条件が満たされるまでポーリングで待機（固定 setTimeout の代替）\nawait waitFor(() =\u003e received.length \u003e= 3);\n\n// タイムアウト・ポーリング間隔のカスタマイズ\nawait waitFor(() =\u003e relay.connectionCount === 0, {\n  timeout: 3000,\n  interval: 20,\n});\n```\n\nスナップショット:\n\n```typescript\nimport { restore, snapshot } from \"@ikuradon/tsunagiya/testing\";\n\nconst snap = snapshot(relay);\n// ... 操作 ...\nrestore(relay, snap);\n```\n\n## Vitest での使い方\n\nnpm パッケージとしてインストールすれば、Vitest でそのまま使えます。\n\n```typescript\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { MockPool } from \"@ikuradon/tsunagiya\";\nimport { waitFor } from \"@ikuradon/tsunagiya/testing\";\n\ndescribe(\"Nostr client\", () =\u003e {\n  let pool: MockPool;\n\n  afterEach(() =\u003e pool?.uninstall());\n\n  it(\"should fetch events from relay\", async () =\u003e {\n    pool = new MockPool();\n    const relay = pool.relay(\"wss://relay.example.com\");\n\n    relay.store({\n      id: \"abc123\",\n      pubkey: \"pubkey1\",\n      kind: 1,\n      content: \"hello nostr\",\n      created_at: 1700000000,\n      tags: [],\n      sig: \"sig1\",\n    });\n\n    pool.install();\n\n    const ws = new WebSocket(\"wss://relay.example.com\");\n    await new Promise\u003cvoid\u003e((resolve) =\u003e {\n      ws.onopen = () =\u003e resolve();\n    });\n\n    const messages: string[] = [];\n    ws.onmessage = (ev) =\u003e messages.push(ev.data as string);\n\n    ws.send(JSON.stringify([\"REQ\", \"sub1\", { kinds: [1] }]));\n    await waitFor(() =\u003e messages.some((m) =\u003e m.includes(\"abc123\")));\n\n    expect(messages.some((m) =\u003e m.includes(\"abc123\"))).toBe(true);\n    ws.close();\n  });\n});\n```\n\n\u003e **Note:** Vitest のデフォルト環境 (`environment: 'node'`) で動作します。\n\u003e `jsdom` や `happy-dom` 環境では WebSocket の競合が起きる可能性があるため、\n\u003e `environment: 'node'` を推奨します。\n\n## E2Eテスト対応\n\n繋ぎ屋(tsunagiya) は以下の主要 Nostr クライアントライブラリとの互換性を E2E\nテストで検証しています。\n\n| ライブラリ  | テストコマンド                  | 検証内容                                            |\n| ----------- | ------------------------------- | --------------------------------------------------- |\n| nostr-tools | `deno task example:nostr-tools` | SimplePool での REQ/EVENT 処理                      |\n| NDK         | `deno task example:ndk`         | NDK インスタンス経由のイベント取得・投稿            |\n| rx-nostr    | `deno task example:rx-nostr`    | RxNostr の Reactive API（createRxNostr / use）      |\n| nostr-fetch | `deno task example:nostr-fetch` | NostrFetcher によるイベント取得（fetch / iterator） |\n\n全ライブラリで正規の BIP-340 Schnorr\n署名を使用し、署名検証を無効化せずにテストを実施しています。\n\n**E2E テストの実行:**\n\n```bash\ndeno task example             # 全 E2E テスト実行\ndeno task test:all            # ユニットテスト + E2E テスト\n```\n\n## 対応NIP\n\n| NIP    | 内容                                  | 対応状況                                                                           |\n| ------ | ------------------------------------- | ---------------------------------------------------------------------------------- |\n| NIP-01 | Basic Protocol                        | EVENT, REQ, CLOSE, EOSE, OK, CLOSED, NOTICE + Event Treatment + Addressable Events |\n| NIP-04 | Encrypted DM ⚠️ deprecated (→ NIP-17) | EventBuilder テンプレート（NIP-17 への移行推奨）                                   |\n| NIP-09 | Event Deletion                        | kind:5 削除リクエスト処理                                                          |\n| NIP-10 | Reply Threading                       | EventBuilder e/p タグ                                                              |\n| NIP-11 | Relay Information                     | setInfo/getInfo + fetch インターセプト                                             |\n| NIP-17 | Private Direct Messages               | EventBuilder テンプレート（chatMessage/seal/giftWrap/dmRelayList）                 |\n| NIP-18 | Reposts                               | EventBuilder テンプレート（repost/genericRepost）                                  |\n| NIP-23 | Long-form Content                     | EventBuilder テンプレート（longFormContent/longFormDraft）                         |\n| NIP-25 | Reactions                             | EventBuilder withReactions / externalReaction                                      |\n| NIP-29 | Relay-based Groups                    | EventBuilder テンプレート                                                          |\n| NIP-30 | Custom Emoji                          | EventBuilder emoji タグ                                                            |\n| NIP-40 | Expiration Timestamp                  | EventBuilder `withExpiration()`                                                    |\n| NIP-42 | AUTH                                  | チャレンジ/レスポンス                                                              |\n| NIP-45 | COUNT                                 | COUNT メッセージ対応                                                               |\n| NIP-50 | Search                                | content 部分一致検索                                                               |\n| NIP-51 | Lists                                 | EventBuilder テンプレート（muteList/pinList/bookmarks/followSet等）                |\n| NIP-52 | Calendar Events                       | EventBuilder テンプレート（全4種対応）                                             |\n| NIP-57 | Lightning Zaps                        | EventBuilder テンプレート                                                          |\n| NIP-65 | Relay List Metadata                   | EventBuilder relayList（kind:10002）                                               |\n\n\u003e **Note:** 旧 NIP-16 (Event Treatment) および旧 NIP-33 (Parameterized\n\u003e Replaceable Events) は現在 NIP-01 に統合されています。本ライブラリの\n\u003e Regular/Replaceable/Ephemeral/Addressable イベント処理は NIP-01\n\u003e 対応の一部です。\n\n## ドキュメント\n\n| ドキュメント                                                                            | 内容                       |\n| --------------------------------------------------------------------------------------- | -------------------------- |\n| [API リファレンス](https://ikuradon.github.io/tsunagiya/reference/api)                  | 全クラス・関数・型の詳細   |\n| [アーキテクチャ](https://ikuradon.github.io/tsunagiya/reference/architecture)           | 内部構造とデータフロー     |\n| [チュートリアル](https://ikuradon.github.io/tsunagiya/guide/tutorial)                   | ステップバイステップガイド |\n| [使用例集](https://ikuradon.github.io/tsunagiya/guide/examples)                         | 実践的な使用例（14例）     |\n| [テストパターン](https://ikuradon.github.io/tsunagiya/guide/test-patterns)              | よくあるテストシナリオ     |\n| [内部リファクタ移行メモ](docs/superpowers/plans/2026-03-20-refactor-migration-notes.md) | maintainers 向けの変更要約 |\n| [ベストプラクティス](https://ikuradon.github.io/tsunagiya/advanced/best-practices)      | テスト設計の指針           |\n| [トラブルシューティング](https://ikuradon.github.io/tsunagiya/help/troubleshooting)     | よくあるエラーと解決方法   |\n| [FAQ](https://ikuradon.github.io/tsunagiya/help/faq)                                    | よくある質問（17問）       |\n| [NIP 対応状況](https://ikuradon.github.io/tsunagiya/reference/nip-support)              | NIP ごとの対応・使用例     |\n| [パフォーマンス](https://ikuradon.github.io/tsunagiya/advanced/performance)             | 大量データの最適化         |\n\n## ライセンス\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fikuradon%2Ftsunagiya","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fikuradon%2Ftsunagiya","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fikuradon%2Ftsunagiya/lists"}