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?
- Set latency to 0 (default). Do not set
latencyfor non-latency tests - Use a short interval for streamEvents (around 10ms)
- Run independent tests concurrently
- 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.
npm install @ikuradon/tsunagiyaSince 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:
pool.relay("wss://known.relay.test"); // only register this
const ws = new WebSocket("wss://unknown.relay.test"); // → error + close(1006)Adding multiple relays:
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:
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:
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:
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); // trueWhen you want to use the real globalThis.WebSocket:
const RealWebSocket = globalThis.WebSocket;
pool.install();
// new WebSocket() is MockWebSocket
// new RealWebSocket() is the real WebSocketEventBuilder default field values:
| Field | Default Value |
|---|---|
id | Random 64-char hex |
pubkey | Random 64-char hex |
created_at | Current time (UNIX seconds) |
kind | Determined by factory method |
tags | [] |
content | "" |
sig | Random 128-char hex |
Code Examples (Per Q&A)
See Examples and Tutorial for detailed code examples.
Related Documentation
- Tutorial — Basic usage
- Troubleshooting — Error resolution
- API Reference — API details