Skip to content

FAQ

Frequently asked questions about tsunagiya.


Q1. What happens when connecting to an unregistered URL?

If you call new WebSocket() with a URL not registered via pool.relay(), it is treated as a connection failure. The onerror event fires, followed by the onclose event (code: 1006). This matches the behavior of failing to connect to a real relay.


Q2. How do I add multiple relays?

Call pool.relay() for each URL. Each relay operates completely independently.


Q3. What should I do if tests are slow?

  1. Set latency to 0 (default). Do not set latency for non-latency tests
  2. Use a short interval for streamEvents (around 10ms)
  3. Run independent tests concurrently
  4. Use pool.reset() to clear state and reuse instances

See the Performance Guide for details.


Q4. Are actual cryptographic signatures verified?

No. tsunagiya is a mock library, so it does not perform cryptographic verification of id, pubkey, or sig. EventBuilder generates random hex strings. If you need to test signature verification, implement it yourself inside an onEVENT handler.


Q5. Can it be used with runtimes other than Deno (Node.js, Bun)?

Yes. It is published as an npm package (@ikuradon/tsunagiya), so you can install it directly with npm install. It works with test frameworks like Vitest and Jest.

bash
npm install @ikuradon/tsunagiya

Since v0.2.0, compatibility with the following major Nostr client libraries has been verified through E2E tests:

  • nostr-tools - REQ/EVENT processing via SimplePool
  • NDK - Event fetch/publish via NDK instance
  • rx-nostr - RxNostr Reactive API (createRxNostr / use)
  • nostr-fetch - Event fetching via NostrFetcher (fetch / iterator)

See Getting Started for Vitest usage.


Q6. Is there a test framework dependency?

No. In addition to Deno.test, you can use any test framework. The assertion functions in @ikuradon/tsunagiya/testing are framework-agnostic, using throw Error.


Q7. If I set onREQ, will store() events be used?

No. Setting an onREQ handler skips auto-filtering (store event search), and only the handler's return value is used. If you want to use both, perform manual filtering inside the handler.


Q8. What happens if I call pool.relay() multiple times with the same URL?

The second and subsequent calls return the existing instance. The options from the first call are used.


Q9. How do I customize the OK response for EVENT messages?

Use the onEVENT handler.


Q10. How do I get the AUTH challenge string?

Receive the ["AUTH", challenge] message via onmessage.


Q11. Do snapshots include handlers and connection state?

No. Snapshots contain only events in the store and received message logs. onREQ, onEVENT handlers and connection state are not saved or restored.


Q12. What is the difference between reset() and restore()?

  • reset(): Clears everything (store, received log, handlers, AUTH state)
  • restore(): Reverts the store and received log to the snapshot point. Handlers are not changed.

Q13. Can binary messages be sent?

MockWebSocket only supports string messages. Calling send() with ArrayBuffer, Blob, etc. will throw an Error. Since the Nostr protocol is JSON string-based, this is generally not an issue.


Q14. What if I want to use globalThis.WebSocket directly during testing?

After pool.install(), all new WebSocket() calls become MockWebSocket. If you want to use the real WebSocket for some connections, save a reference before installing.


Q15. Are there any external dependencies?

Zero. tsunagiya has no dependency on external packages whatsoever. The @std/assert in the testing module is for tsunagiya's own tests and is not a library dependency.


Q16. Any notes for using tsunagiya in CI?

No special configuration is needed. Replacing globalThis.WebSocket is contained within the process, so network access is not required. It works without the --allow-net flag.


Q17. What are the field values of events generated by EventBuilder?

Connection to unregistered URL:

typescript
pool.relay("wss://known.relay.test"); // only register this
const ws = new WebSocket("wss://unknown.relay.test"); // → error + close(1006)

Adding multiple relays:

typescript
const pool = new MockPool();
const relay1 = pool.relay("wss://relay1.example.com");
const relay2 = pool.relay("wss://relay2.example.com");
const relay3 = pool.relay("wss://relay3.example.com");

Custom response with onEVENT:

typescript
relay.onEVENT((event) => {
  if (event.kind === 1) {
    return ["OK", event.id, true, ""];
  }
  return ["OK", event.id, false, "blocked: kind not allowed"];
});

Getting the AUTH challenge:

typescript
ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  if (msg[0] === "AUTH") {
    const challenge = msg[1]; // challenge string
  }
};

Calling the same URL multiple times:

typescript
const r1 = pool.relay("wss://relay.example.com", { latency: 100 });
const r2 = pool.relay("wss://relay.example.com"); // same instance as r1
console.log(r1 === r2); // true

When you want to use the real globalThis.WebSocket:

typescript
const RealWebSocket = globalThis.WebSocket;
pool.install();
// new WebSocket() is MockWebSocket
// new RealWebSocket() is the real WebSocket

EventBuilder default field values:

FieldDefault Value
idRandom 64-char hex
pubkeyRandom 64-char hex
created_atCurrent time (UNIX seconds)
kindDetermined by factory method
tags[]
content""
sigRandom 128-char hex

Code Examples (Per Q&A)

See Examples and Tutorial for detailed code examples.


MIT License