diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 6f25457..ade7774 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -87,11 +87,6 @@ export default defineConfig({ {text: "NIP 59", link: "/signer/nip-59"}, ], }, - { - text: "@welshman/relay", - link: "/relay/", - items: [], - }, { text: "@welshman/router", link: "/router/", diff --git a/docs/getting-started.md b/docs/getting-started.md index 6bf41a1..daabf40 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -7,9 +7,6 @@ Welshman is modular - install only what you need: # Core nostr utilities (events, filters, tags) npm i @welshman/util -# In-memory event store and relay adapter -npm i @welshman/relay - # Networking and relay management npm i @welshman/net diff --git a/docs/index.md b/docs/index.md index 790ee77..3a9514b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,9 +30,6 @@ features: - title: "@welshman/signer" details: Implementations of various nostr signing methods (NIP-01, NIP-07, NIP-46, NIP-55). link: "/signer" - - title: "@welshman/relay" - details: In-memory relay and event store. - link: "/relay" - title: "@welshman/router" details: Tools for relay selection. link: "/router" diff --git a/docs/relay/index.md b/docs/relay/index.md deleted file mode 100644 index 7dd9203..0000000 --- a/docs/relay/index.md +++ /dev/null @@ -1,143 +0,0 @@ -# @welshman/relay - -[![version](https://badgen.net/npm/v/@welshman/relay)](https://npmjs.com/package/@welshman/relay) - -A few utilites for storing nostr events in memory. - -## What's Included - -- **Event Store** - A Repository class which stores events in memory -- **Relay Adapter** - A LocalRelay class which adapts nostr messages to the repository -- **Event Tracker** - A Tracker class for managing which events have been seen from which relays -- **Gift Wrap Manager** - A WrapManager class for tracking and unwrapping NIP-59 gift wrapped events - -## Quick Example - -```typescript -import {Repository, LocalRelay} from "@welshman/relay" - -// Create an in-memory event repository -const repository = Repository.get() - -// Publish events directly to the repository -const textNote = { - id: "event123", - pubkey: "author-pubkey", - created_at: Math.floor(Date.now() / 1000), - kind: 1, - tags: [], - content: "Hello, world!", - sig: "signature" -} - -repository.publish(textNote) - -// Query events using filters -const recentNotes = repository.query([{kinds: [1], limit: 10}]) -console.log(`Found ${recentNotes.length} text notes`) - -// Listen for repository updates -repository.on("update", ({added, removed}) => { - console.log(`Added ${added.length} events, removed ${removed.size} events`) -}) - -// Create a local relay that adapts Nostr messages to the repository -const relay = new LocalRelay(repository) - -// Listen for relay messages -relay.on("EVENT", (subId, event) => { - console.log(`Received event ${event.id} for subscription ${subId}`) -}) - -relay.on("OK", (eventId, success, message) => { - console.log(`Event ${eventId} ${success ? "accepted" : "rejected"}: ${message}`) -}) - -// Use relay protocol to publish and subscribe -relay.send("EVENT", { - id: "event456", - pubkey: "another-author", - created_at: Math.floor(Date.now() / 1000), - kind: 1, - tags: [["t", "welshman"]], - content: "Using LocalRelay!", - sig: "signature" -}) - -// Subscribe to events with hashtag -relay.send("REQ", "tagged", {kinds: [1], "#t": ["welshman"]}) -``` - -### Tracking Events Across Relays - -```typescript -import {Tracker} from "@welshman/relay" - -const tracker = new Tracker() - -// Track events from different relays -const isDuplicate1 = tracker.track("event123", "wss://relay1.com") // false -const isDuplicate2 = tracker.track("event123", "wss://relay2.com") // false -const isDuplicate3 = tracker.track("event123", "wss://relay1.com") // true (duplicate) - -// Check which relays have sent an event -const relays = tracker.getRelays("event123") // Set(["wss://relay1.com", "wss://relay2.com"]) - -// Copy relay tracking from one event to another (useful for wrapped events) -tracker.copy("wrap-event-id", "rumor-event-id") -``` - -### Managing Gift Wrapped Events - -The WrapManager handles NIP-59 gift wrapped events, automatically unwrapping incoming wrapped events and tracking the relationship between wraps and their inner rumors. - -```typescript -import {Repository, LocalRelay, Tracker, WrapManager} from "@welshman/relay" -import {ISigner} from "@welshman/signer" - -const repository = Repository.get() -const relay = new LocalRelay(repository) -const tracker = new Tracker() - -// Create a wrap manager with a function to get signers for different pubkeys -const wrapManager = new WrapManager({ - relay, - tracker, - getSigner: (pubkey: string) => { - // Return the appropriate signer for this pubkey - return mySignerMap.get(pubkey) - } -}) - -// When you publish a wrapped event, track it -wrapManager.add({ - recipient: recipientPubkey, - wrap: wrappedEvent, - rumor: innerEvent -}) - -// When you receive a wrapped event, unwrap it -await wrapManager.unwrap(receivedWrapEvent) - -// The rumor will be automatically published to the repository -// and relay tracking will be copied from the wrap to the rumor - -// Remove wraps by various criteria -wrapManager.remove(wrapId) -wrapManager.removeByRumorId(rumorId) - -// Listen for wrap manager events -wrapManager.on("add", (wrapItem) => { - console.log("Wrap added:", wrapItem) -}) - -wrapManager.on("remove", (wrapItem) => { - console.log("Wrap removed:", wrapItem) -}) -``` - -## Installation - -```bash -npm install @welshman/relay -``` diff --git a/docs/store/collection.md b/docs/store/collection.md index 72130fd..570ecd0 100644 --- a/docs/store/collection.md +++ b/docs/store/collection.md @@ -42,7 +42,7 @@ Creates a cached loader function with staleness checking and exponential backoff import {writable} from 'svelte/store' import {derived, readable} from "svelte/store" import {readProfile, PROFILE, PublishedProfile} from "@welshman/util" -import {Repository} from "@welshman/relay" +import {Repository} from "@welshman/net" import {deriveEventsMapped, collection, withGetter} from "@welshman/store" const repository = new Repository() diff --git a/docs/store/repository.md b/docs/store/repository.md index 5e73c35..240106d 100644 --- a/docs/store/repository.md +++ b/docs/store/repository.md @@ -39,7 +39,7 @@ Creates a reactive store that tracks whether an event is deleted by address. ## Example ```typescript -import {Repository} from "@welshman/relay" +import {Repository} from "@welshman/net" import {deriveEventsMapped, deriveEvents} from "@welshman/store" import {readProfile, PROFILE} from "@welshman/util" @@ -71,4 +71,4 @@ profiles.subscribe(profiles => { // Add some events to the repository repository.publish(someTextNoteEvent) repository.publish(someProfileEvent) -``` \ No newline at end of file +``` diff --git a/packages/app/__tests__/thunk.test.ts b/packages/app/__tests__/thunk.test.ts index b5c2c06..037c0c5 100644 --- a/packages/app/__tests__/thunk.test.ts +++ b/packages/app/__tests__/thunk.test.ts @@ -1,6 +1,5 @@ -import {PublishStatus} from "@welshman/net" +import {PublishStatus, LOCAL_RELAY_URL} from "@welshman/net" import {NOTE, DIRECT_MESSAGE, WRAP, makeEvent} from "@welshman/util" -import {LOCAL_RELAY_URL} from "@welshman/relay" import {getPubkey, makeSecret, prep} from "@welshman/signer" import {afterEach, beforeEach, describe, expect, it, vi} from "vitest" import {repository, tracker} from "../src/core" diff --git a/packages/app/package.json b/packages/app/package.json index 632ea75..1e95f60 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -23,7 +23,6 @@ "@types/throttle-debounce": "^5.0.2", "@welshman/feeds": "workspace:*", "@welshman/lib": "workspace:*", - "@welshman/relay": "workspace:*", "@welshman/router": "workspace:*", "@welshman/net": "workspace:*", "@welshman/signer": "workspace:*", diff --git a/packages/app/src/commands.ts b/packages/app/src/commands.ts index ad48410..4d0099d 100644 --- a/packages/app/src/commands.ts +++ b/packages/app/src/commands.ts @@ -208,7 +208,7 @@ export type SendWrappedOptions = Omit & { recipients: string[] } -export const sendWrapped = async ({event, recipients, ...options}: SendWrappedOptions) => +export const sendWrapped = ({event, recipients, ...options}: SendWrappedOptions) => new MergedThunk( uniq(recipients).map(recipient => { const relays = Router.get().PubkeyInbox(recipient).getUrls() diff --git a/packages/app/src/core.ts b/packages/app/src/core.ts index cfc7e15..d9b05a0 100644 --- a/packages/app/src/core.ts +++ b/packages/app/src/core.ts @@ -1,13 +1,11 @@ import {throttle} from "@welshman/lib" -import {Repository, LocalRelay, Tracker} from "@welshman/relay" +import {Repository, Tracker} from "@welshman/net" import {custom} from "@welshman/store" export const tracker = new Tracker() export const repository = Repository.get() -export const relay = new LocalRelay(repository) - // Adapt objects to stores export const makeRepositoryStore = ({throttle: t = 300}: {throttle?: number} = {}) => diff --git a/packages/app/src/session.ts b/packages/app/src/session.ts index aa1e795..f0a447e 100644 --- a/packages/app/src/session.ts +++ b/packages/app/src/session.ts @@ -13,8 +13,8 @@ import { getPubkey, ISigner, } from "@welshman/signer" -import {WrapManager} from "@welshman/relay" -import {relay, tracker} from "./core.js" +import {WrapManager} from "@welshman/net" +import {tracker, repository} from "./core.js" export enum SessionMethod { Nip01 = "nip01", @@ -279,7 +279,7 @@ export const nip44EncryptToSelf = (payload: string) => { // Gift wrap utilities -export const wrapManager = new WrapManager({relay, tracker}) +export const wrapManager = new WrapManager({repository, tracker}) export const shouldUnwrap = withGetter(writable(false)) diff --git a/packages/app/tsconfig.build.json b/packages/app/tsconfig.build.json index 3b1534b..524ea02 100644 --- a/packages/app/tsconfig.build.json +++ b/packages/app/tsconfig.build.json @@ -6,7 +6,6 @@ "paths": { "@welshman/feeds": ["../feeds/src/index.js"], "@welshman/lib": ["../lib/src/index.js"], - "@welshman/relay": ["../relay/src/index.js"], "@welshman/net": ["../net/src/index.js"], "@welshman/signer": ["../signer/src/index.js"], "@welshman/store": ["../store/src/index.js"], diff --git a/packages/feeds/package.json b/packages/feeds/package.json index ad45e16..46088af 100644 --- a/packages/feeds/package.json +++ b/packages/feeds/package.json @@ -22,7 +22,6 @@ "dependencies": { "@welshman/lib": "workspace:*", "@welshman/net": "workspace:*", - "@welshman/relay": "workspace:*", "@welshman/router": "workspace:*", "@welshman/signer": "workspace:*", "@welshman/util": "workspace:*", diff --git a/packages/feeds/src/controller.ts b/packages/feeds/src/controller.ts index d02f8d5..2eb3ef1 100644 --- a/packages/feeds/src/controller.ts +++ b/packages/feeds/src/controller.ts @@ -11,7 +11,7 @@ import { now, } from "@welshman/lib" import {EPOCH, trimFilters, guessFilterDelta, TrustedEvent, Filter} from "@welshman/util" -import {Tracker} from "@welshman/relay" +import {Tracker} from "@welshman/net" import {Feed, FeedType, RequestItem} from "./core.js" import {FeedCompiler, FeedCompilerOptions} from "./compiler.js" import {requestPage} from "./request.js" diff --git a/packages/feeds/src/request.ts b/packages/feeds/src/request.ts index 0f24215..b4b3648 100644 --- a/packages/feeds/src/request.ts +++ b/packages/feeds/src/request.ts @@ -10,9 +10,8 @@ import { RELAYS, } from "@welshman/util" import {Nip01Signer, ISigner} from "@welshman/signer" -import {LOCAL_RELAY_URL, Tracker} from "@welshman/relay" import {Router, getFilterSelections, addMinimalFallbacks} from "@welshman/router" -import {AdapterContext, request, publish} from "@welshman/net" +import {LOCAL_RELAY_URL, Tracker, AdapterContext, request, publish} from "@welshman/net" export type RequestPageOptions = { filters: Filter[] diff --git a/packages/feeds/tsconfig.build.json b/packages/feeds/tsconfig.build.json index 99cc484..44fb99d 100644 --- a/packages/feeds/tsconfig.build.json +++ b/packages/feeds/tsconfig.build.json @@ -5,6 +5,9 @@ "outDir": "./dist", "paths": { "@welshman/lib": ["../lib/src/index.js"], + "@welshman/net": ["../net/src/index.js"], + "@welshman/router": ["../router/src/index.js"], + "@welshman/signer": ["../signer/src/index.js"], "@welshman/util": ["../util/src/index.js"] } }, diff --git a/packages/net/__tests__/adapter.test.ts b/packages/net/__tests__/adapter.test.ts index 1ccf2f1..f2f42dd 100644 --- a/packages/net/__tests__/adapter.test.ts +++ b/packages/net/__tests__/adapter.test.ts @@ -1,7 +1,8 @@ -import EventEmitter from "events" import {describe, expect, it, vi, beforeEach, afterEach} from "vitest" -import {LocalRelay, Repository, LOCAL_RELAY_URL} from "@welshman/relay" +import {makeEvent} from "@welshman/util" +import {prep, getPubkey, makeSecret} from "@welshman/signer" import {AdapterEvent, SocketAdapter, LocalAdapter, getAdapter} from "../src/adapter" +import {Repository, LOCAL_RELAY_URL} from "../src/repository" import {ClientMessage, RelayMessage} from "../src/message" import {Socket, SocketEvent} from "../src/socket" import {Pool} from "../src/pool" @@ -69,17 +70,13 @@ describe("SocketAdapter", () => { }) describe("LocalAdapter", () => { - let relay: LocalRelay & EventEmitter + let repository: Repository let adapter: LocalAdapter beforeEach(() => { - const mockRelay = new EventEmitter() - Object.assign(mockRelay, { - send: vi.fn(), - removeAllListeners: vi.fn(), - }) - relay = mockRelay as unknown as LocalRelay & EventEmitter - adapter = new LocalAdapter(relay) + repository = new Repository() + adapter = new LocalAdapter(repository) + vi.useFakeTimers() }) afterEach(() => { @@ -88,32 +85,35 @@ describe("LocalAdapter", () => { }) it("should initialize with correct relay", () => { - expect(adapter.relay).toBe(relay) expect(adapter.urls).toEqual([LOCAL_RELAY_URL]) expect(adapter.sockets).toEqual([]) }) it("should forward received messages", () => { const receiveSpy = vi.fn() + const pubkey = getPubkey(makeSecret()) + const event = prep(makeEvent(1), pubkey) + + adapter.send(["REQ", "r1", {kinds: [1]}]) + adapter.send(["REQ", "r2", {kinds: [2]}]) adapter.on(AdapterEvent.Receive, receiveSpy) + repository.publish(event) - const message: RelayMessage = ["EVENT", "123", {id: "123", kind: 1}] - relay.emit("*", ...message) - - expect(receiveSpy).toHaveBeenCalledWith(message, LOCAL_RELAY_URL) + expect(receiveSpy).toHaveBeenCalledTimes(1) + expect(receiveSpy).toHaveBeenCalledWith(["EVENT", "r1", event], LOCAL_RELAY_URL) }) - it("should send messages to relay", () => { - const message: ClientMessage = ["EVENT", {id: "123", kind: 1}] - adapter.send(message) + it("should send messages to relay", async () => { + const publishSpy = vi.spyOn(repository, "publish") + const pubkey = getPubkey(makeSecret()) + const event = prep(makeEvent(1), pubkey) - expect(relay.send).toHaveBeenCalledWith("EVENT", message[1]) - }) + adapter.send(["EVENT", event]) - it("should cleanup properly", () => { - const removeListenersSpy = vi.spyOn(adapter, "removeAllListeners") - adapter.cleanup() - expect(removeListenersSpy).toHaveBeenCalled() + await vi.runAllTimersAsync() + + expect(publishSpy).toHaveBeenCalledTimes(1) + expect(publishSpy).toHaveBeenCalledWith(event) }) }) diff --git a/packages/relay/__tests__/repository.test.ts b/packages/net/__tests__/repository.test.ts similarity index 100% rename from packages/relay/__tests__/repository.test.ts rename to packages/net/__tests__/repository.test.ts diff --git a/packages/net/package.json b/packages/net/package.json index e9578ff..e705fe0 100644 --- a/packages/net/package.json +++ b/packages/net/package.json @@ -21,7 +21,6 @@ }, "dependencies": { "@welshman/lib": "workspace:*", - "@welshman/relay": "workspace:*", "@welshman/util": "workspace:*", "events": "^3.3.0", "isomorphic-ws": "^5.0.0" diff --git a/packages/net/src/adapter.ts b/packages/net/src/adapter.ts index 5d83dfd..8ec02fb 100644 --- a/packages/net/src/adapter.ts +++ b/packages/net/src/adapter.ts @@ -1,8 +1,16 @@ import EventEmitter from "events" -import {call, mergeRight, on} from "@welshman/lib" -import {isRelayUrl} from "@welshman/util" -import {LocalRelay, LOCAL_RELAY_URL} from "@welshman/relay" -import {RelayMessage, ClientMessage} from "./message.js" +import {call, sleep, mergeRight, on} from "@welshman/lib" +import {isRelayUrl, matchFilters, Filter} from "@welshman/util" +import {LOCAL_RELAY_URL, Repository} from "./repository" +import { + RelayMessage, + RelayMessageType, + ClientMessage, + ClientMessageType, + ClientEvent, + ClientReq, + ClientClose, +} from "./message.js" import {Socket, SocketEvent} from "./socket.js" import {Unsubscriber} from "./util.js" import {netContext, NetContext} from "./context.js" @@ -53,12 +61,20 @@ export class SocketAdapter extends AbstractAdapter { } export class LocalAdapter extends AbstractAdapter { - constructor(readonly relay: LocalRelay) { + subs = new Map() + + constructor(readonly repository: Repository) { super() this._unsubscribers.push( - on(relay, "*", (...message: RelayMessage) => { - this.emit(AdapterEvent.Receive, message, LOCAL_RELAY_URL) + on(repository, "update", ({added}) => { + for (const [subId, filters] of this.subs.entries()) { + for (const event of added) { + if (matchFilters(filters, event)) { + this.#receive([RelayMessageType.Event, subId, event]) + } + } + } }), ) } @@ -72,9 +88,42 @@ export class LocalAdapter extends AbstractAdapter { } send(message: ClientMessage) { - const [type, ...rest] = message + switch (message[0]) { + case ClientMessageType.Event: + return this.#handleEVENT(message as ClientEvent) + case ClientMessageType.Close: + return this.#handleCLOSE(message as ClientClose) + case ClientMessageType.Req: + return this.#handleREQ(message as ClientReq) + } + } - this.relay.send(type, ...rest) + #receive(message: RelayMessage) { + this.emit(AdapterEvent.Receive, message, LOCAL_RELAY_URL) + } + + #handleEVENT([_, event]: ClientEvent) { + this.repository.publish(event) + + // Callers generally expect async relays + sleep(1).then(() => this.#receive([RelayMessageType.Ok, event.id, true, ""])) + } + + #handleCLOSE([_, subId]: ClientClose) { + this.subs.delete(subId) + } + + #handleREQ([_, subId, ...filters]: ClientReq) { + this.subs.set(subId, filters) + + // Callers generally expect async relays + sleep(1).then(() => { + for (const event of this.repository.query(filters)) { + this.#receive([RelayMessageType.Event, subId, event]) + } + + this.#receive([RelayMessageType.Eose, subId]) + }) } } @@ -113,7 +162,7 @@ export const getAdapter = (url: string, adapterContext: AdapterContext = {}) => } if (url === LOCAL_RELAY_URL) { - return new LocalAdapter(new LocalRelay(context.repository)) + return new LocalAdapter(context.repository) } if (isRelayUrl(url)) { diff --git a/packages/net/src/context.ts b/packages/net/src/context.ts index 60b9736..c69f954 100644 --- a/packages/net/src/context.ts +++ b/packages/net/src/context.ts @@ -1,6 +1,6 @@ -import {Repository} from "@welshman/relay" import {verifyEvent, TrustedEvent} from "@welshman/util" import {AbstractAdapter} from "./adapter.js" +import {Repository} from "./repository.js" import {Pool} from "./pool.js" export type NetContext = { diff --git a/packages/net/src/index.ts b/packages/net/src/index.ts index 3f9034b..b5bb628 100644 --- a/packages/net/src/index.ts +++ b/packages/net/src/index.ts @@ -8,4 +8,7 @@ export * from "./policy.js" export * from "./pool.js" export * from "./publish.js" export * from "./socket.js" +export * from "./repository.js" export * from "./request.js" +export * from "./tracker.js" +export * from "./wrapManager.js" diff --git a/packages/relay/src/repository.ts b/packages/net/src/repository.ts similarity index 100% rename from packages/relay/src/repository.ts rename to packages/net/src/repository.ts diff --git a/packages/net/src/request.ts b/packages/net/src/request.ts index 8d761c7..c9d09be 100644 --- a/packages/net/src/request.ts +++ b/packages/net/src/request.ts @@ -18,11 +18,11 @@ import { deduplicateEvents, getFilterResultCardinality, } from "@welshman/util" -import {Tracker} from "@welshman/relay" import {RelayMessage, ClientMessageType, isRelayEvent, isRelayEose} from "./message.js" import {getAdapter, AdapterContext, AdapterEvent} from "./adapter.js" import {SocketEvent, SocketStatus} from "./socket.js" import {netContext} from "./context.js" +import {Tracker} from "./tracker.js" export type BaseRequestOptions = { signal?: AbortSignal diff --git a/packages/relay/src/tracker.ts b/packages/net/src/tracker.ts similarity index 100% rename from packages/relay/src/tracker.ts rename to packages/net/src/tracker.ts diff --git a/packages/relay/src/wrapManager.ts b/packages/net/src/wrapManager.ts similarity index 82% rename from packages/relay/src/wrapManager.ts rename to packages/net/src/wrapManager.ts index 0cf9bb2..9d3f6d3 100644 --- a/packages/relay/src/wrapManager.ts +++ b/packages/net/src/wrapManager.ts @@ -1,7 +1,7 @@ import {Emitter, remove, omit} from "@welshman/lib" import {HashedEvent, SignedEvent} from "@welshman/util" import {Tracker} from "./tracker.js" -import {LocalRelay} from "./relay.js" +import {Repository} from "./repository.js" export type WrapItem = Omit & { rumorId: string @@ -11,24 +11,30 @@ export type WrapItem = Omit & { export type WrapReference = string[] export type WrapManagerOptions = { - relay: LocalRelay + repository: Repository tracker: Tracker } export class WrapManager extends Emitter { _wrapIndex = new Map() _rumorIndex = new Map() - _recipientIndex = new Map() constructor(readonly options: WrapManagerOptions) { super() } - getRumor = (id: string) => { - const wrapItem = this._wrapIndex.get(id) + // Reading/exporting + + dump = () => Array.from(this._wrapIndex.values()) + + getWraps = (rumorId: string) => + this._rumorIndex.get(rumorId).map(wrapId => this._wrapIndex.get(wrapId)!) + + getRumor = (wrapId: string) => { + const wrapItem = this._wrapIndex.get(wrapId) if (wrapItem) { - return this.options.relay.repository.getEvent(wrapItem.rumorId) + return this.options.repository.getEvent(wrapItem.rumorId) } } @@ -55,8 +61,8 @@ export class WrapManager extends Emitter { this._add(wrapItem) - // Send via our relay so that listeners get notified - this.options.relay.send("EVENT", rumor) + // Save to our repository + this.options.repository.publish(rumor) // Mark the rumor as having come from the wrap's urls this.options.tracker.copy(wrap.id, rumor.id) @@ -71,7 +77,7 @@ export class WrapManager extends Emitter { if (wrapItem) { this._remove(wrapItem) - this.options.relay.repository.removeEvent(wrapItem.rumorId) + this.options.repository.removeEvent(wrapItem.rumorId) this.emit("remove", wrapItem) } } diff --git a/packages/net/tsconfig.build.json b/packages/net/tsconfig.build.json index 12be0a5..99cc484 100644 --- a/packages/net/tsconfig.build.json +++ b/packages/net/tsconfig.build.json @@ -5,8 +5,7 @@ "outDir": "./dist", "paths": { "@welshman/lib": ["../lib/src/index.js"], - "@welshman/util": ["../util/src/index.js"], - "@welshman/relay": ["../relay/src/index.js"] + "@welshman/util": ["../util/src/index.js"] } }, diff --git a/packages/relay/.eslintignore b/packages/relay/.eslintignore deleted file mode 100644 index 22d79d5..0000000 --- a/packages/relay/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -build -normalize-url -Negentropy.ts -__tests__ diff --git a/packages/relay/__tests__/relay.test.ts b/packages/relay/__tests__/relay.test.ts deleted file mode 100644 index ed5173f..0000000 --- a/packages/relay/__tests__/relay.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import {describe, it, expect, beforeEach, vi, afterEach} from "vitest" -import {now} from "@welshman/lib" -import type {TrustedEvent} from "@welshman/util" -import {LocalRelay} from "../src/relay" -import {Repository} from "../src/repository" - -describe("LocalRelay", () => { - beforeEach(() => { - vi.clearAllMocks() - vi.useFakeTimers() - }) - - afterEach(() => { - vi.useRealTimers() - }) - - // Realistic Nostr data - const pubkey = "ee".repeat(32) - const id = "ff".repeat(32) - const sig = "00".repeat(64) - const currentTime = now() - - const createEvent = (overrides = {}): TrustedEvent => ({ - id: id, - pubkey: pubkey, - created_at: currentTime, - kind: 1, - tags: [], - content: "Hello Nostr!", - sig: sig, - ...overrides, - }) - - describe("LocalRelay class", () => { - let relay: LocalRelay - let repository: Repository - - beforeEach(() => { - repository = new Repository() - relay = new LocalRelay(repository) - }) - - describe("EVENT handling", () => { - it("should publish events to repository", async () => { - const event = createEvent() - const publishSpy = vi.spyOn(repository, "publish") - - relay.send("EVENT", event) - - expect(publishSpy).toHaveBeenCalledWith(event) - - // Should emit OK - const okHandler = vi.fn() - relay.on("OK", okHandler) - - // Wait for async operations - await vi.runAllTimersAsync() - - expect(okHandler).toHaveBeenCalledWith(event.id, true, "") - }) - - it("should notify matching subscribers", async () => { - const event = createEvent() - const subId = "test-sub" - const filter = {kinds: [1]} - - relay.send("REQ", subId, filter) - - const eventHandler = vi.fn() - relay.on("EVENT", eventHandler) - - relay.send("EVENT", event) - - await vi.runAllTimersAsync() - - expect(eventHandler).toHaveBeenCalledWith(subId, event) - }) - - it("should not notify for deleted events", async () => { - const event = createEvent() - repository.removeEvent(event.id) - - const eventHandler = vi.fn() - relay.on("EVENT", eventHandler) - - relay.send("EVENT", event) - - await vi.runAllTimersAsync() - - expect(eventHandler).not.toHaveBeenCalled() - }) - }) - - describe("REQ handling", () => { - it("should handle subscription requests", async () => { - const event = createEvent() - repository.publish(event) - - const subId = "test-sub" - const filter = {kinds: [1]} - - const eventHandler = vi.fn() - const eoseHandler = vi.fn() - - relay.on("EVENT", eventHandler) - relay.on("EOSE", eoseHandler) - - relay.send("REQ", subId, filter) - - await vi.runAllTimersAsync() - - expect(eventHandler).toHaveBeenCalledWith(subId, event) - expect(eoseHandler).toHaveBeenCalledWith(subId) - }) - - it("should handle multiple filters", async () => { - const event1 = createEvent({kind: 1}) - const event2 = createEvent({kind: 2, id: "ee".repeat(31)}) - repository.publish(event1) - repository.publish(event2) - - const subId = "test-sub" - const filters = [{kinds: [1]}, {kinds: [2]}] - - const eventHandler = vi.fn() - relay.on("EVENT", eventHandler) - - relay.send("REQ", subId, ...filters) - - await vi.runAllTimersAsync() - - expect(eventHandler).toHaveBeenCalledTimes(2) - }) - }) - - describe("CLOSE handling", () => { - it("should close subscriptions", async () => { - const subId = "test-sub" - relay.send("REQ", subId, {kinds: [1]}) - relay.send("CLOSE", subId) - - await vi.runAllTimersAsync() - - const event = createEvent() - const eventHandler = vi.fn() - relay.on("EVENT", eventHandler) - - relay.send("EVENT", event) - - await vi.runAllTimersAsync() - - expect(eventHandler).not.toHaveBeenCalled() - }) - }) - }) -}) diff --git a/packages/relay/package.json b/packages/relay/package.json deleted file mode 100644 index 6847a24..0000000 --- a/packages/relay/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@welshman/relay", - "version": "0.5.4", - "author": "hodlbod", - "license": "MIT", - "description": "An in-memory nostr relay implementation.", - "publishConfig": { - "access": "public" - }, - "type": "module", - "main": "dist/relay/src/index.js", - "types": "dist/relay/src/index.d.ts", - "files": [ - "dist" - ], - "scripts": { - "build": "pnpm run clean && pnpm run compile --force", - "clean": "rimraf ./dist", - "compile": "tsc -b tsconfig.build.json", - "prepublishOnly": "pnpm run build" - }, - "dependencies": { - "@welshman/lib": "workspace:*", - "@welshman/util": "workspace:*", - "@welshman/signer": "workspace:*" - }, - "devDependencies": { - "rimraf": "~6.0.0", - "typescript": "~5.8.0" - } -} diff --git a/packages/relay/src/index.ts b/packages/relay/src/index.ts deleted file mode 100644 index b8b3efe..0000000 --- a/packages/relay/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./relay.js" -export * from "./repository.js" -export * from "./tracker.js" -export * from "./wrapManager.js" diff --git a/packages/relay/src/relay.ts b/packages/relay/src/relay.ts deleted file mode 100644 index 59f18e8..0000000 --- a/packages/relay/src/relay.ts +++ /dev/null @@ -1,56 +0,0 @@ -import {Emitter, sleep} from "@welshman/lib" -import {Filter, TrustedEvent, matchFilters} from "@welshman/util" -import {Repository} from "./repository.js" - -export class LocalRelay extends Emitter { - subs = new Map() - - constructor(readonly repository: Repository) { - super() - } - - send(type: string, ...message: any[]) { - switch (type) { - case "EVENT": - return this.handleEVENT(message as [TrustedEvent]) - case "CLOSE": - return this.handleCLOSE(message as [string]) - case "REQ": - return this.handleREQ(message as [string, ...Filter[]]) - } - } - - handleEVENT([event]: [TrustedEvent]) { - this.repository.publish(event) - - // Callers generally expect async relays - void sleep(1).then(() => { - this.emit("OK", event.id, true, "") - - if (!this.repository.isDeleted(event)) { - for (const [subId, filters] of this.subs.entries()) { - if (matchFilters(filters, event)) { - this.emit("EVENT", subId, event) - } - } - } - }) - } - - handleCLOSE([subId]: [string]) { - this.subs.delete(subId) - } - - handleREQ([subId, ...filters]: [string, ...Filter[]]) { - this.subs.set(subId, filters) - - // Callers generally expect async relays - void sleep(1).then(() => { - for (const event of this.repository.query(filters)) { - this.emit("EVENT", subId, event) - } - - this.emit("EOSE", subId) - }) - } -} diff --git a/packages/relay/tsconfig.build.json b/packages/relay/tsconfig.build.json deleted file mode 100644 index 99cc484..0000000 --- a/packages/relay/tsconfig.build.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig.build.json", - - "compilerOptions": { - "outDir": "./dist", - "paths": { - "@welshman/lib": ["../lib/src/index.js"], - "@welshman/util": ["../util/src/index.js"] - } - }, - - "include": [ - "src/**/*" - ] -} diff --git a/packages/relay/tsconfig.json b/packages/relay/tsconfig.json deleted file mode 100644 index 4082f16..0000000 --- a/packages/relay/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../tsconfig.json" -} diff --git a/packages/router/package.json b/packages/router/package.json index 0809f6b..61440b6 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -22,7 +22,7 @@ "dependencies": { "@welshman/lib": "workspace:*", "@welshman/util": "workspace:*", - "@welshman/relay": "workspace:*" + "@welshman/net": "workspace:*" }, "devDependencies": { "rimraf": "~6.0.0", diff --git a/packages/router/src/index.ts b/packages/router/src/index.ts index ee6925e..1c6e8c7 100644 --- a/packages/router/src/index.ts +++ b/packages/router/src/index.ts @@ -35,7 +35,7 @@ import { getPubkeyTags, RelayMode, } from "@welshman/util" -import {Repository} from "@welshman/relay" +import {Repository} from "@welshman/net" export const INDEXED_KINDS = [PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS] diff --git a/packages/signer/tsconfig.build.json b/packages/signer/tsconfig.build.json index 451b678..86e2ed1 100644 --- a/packages/signer/tsconfig.build.json +++ b/packages/signer/tsconfig.build.json @@ -6,7 +6,6 @@ "paths": { "@welshman/lib": ["../lib/src/index.js"], "@welshman/util": ["../util/src/index.js"], - "@welshman/relay": ["../relay/src/index.js"], "@welshman/net": ["../net/src/index.js"] } }, diff --git a/packages/store/__tests__/index.test.ts b/packages/store/__tests__/index.test.ts index ec3a774..cc02dee 100644 --- a/packages/store/__tests__/index.test.ts +++ b/packages/store/__tests__/index.test.ts @@ -1,5 +1,5 @@ import {TrustedEvent} from "@welshman/util" -import {Repository} from "@welshman/relay" +import {Repository} from "@welshman/net" import {get} from "svelte/store" import {afterEach, beforeEach, describe, expect, it, vi} from "vitest" import { diff --git a/packages/store/package.json b/packages/store/package.json index 8830e25..453b3d4 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -22,7 +22,7 @@ "dependencies": { "@welshman/lib": "workspace:*", "@welshman/util": "workspace:*", - "@welshman/relay": "workspace:*", + "@welshman/net": "workspace:*", "svelte": "^4.2.18" }, "devDependencies": { diff --git a/packages/store/src/repository.ts b/packages/store/src/repository.ts index a729eb0..d0bff0b 100644 --- a/packages/store/src/repository.ts +++ b/packages/store/src/repository.ts @@ -1,7 +1,7 @@ import {derived} from "svelte/store" import {sortBy, identity, ensurePlural, removeNil, batch, partition, first} from "@welshman/lib" -import {Repository} from "@welshman/relay" import {matchFilters, getIdAndAddress, getIdFilters, Filter, TrustedEvent} from "@welshman/util" +import {Repository} from "@welshman/net" import {custom} from "./custom.js" export type DeriveEventsMappedOptions = { diff --git a/packages/store/tsconfig.build.json b/packages/store/tsconfig.build.json index 12be0a5..86e2ed1 100644 --- a/packages/store/tsconfig.build.json +++ b/packages/store/tsconfig.build.json @@ -6,7 +6,7 @@ "paths": { "@welshman/lib": ["../lib/src/index.js"], "@welshman/util": ["../util/src/index.js"], - "@welshman/relay": ["../relay/src/index.js"] + "@welshman/net": ["../net/src/index.js"] } }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13b2fb4..eecdeed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,9 +74,6 @@ importers: '@welshman/net': specifier: workspace:* version: link:../net - '@welshman/relay': - specifier: workspace:* - version: link:../relay '@welshman/router': specifier: workspace:* version: link:../router @@ -194,9 +191,6 @@ importers: '@welshman/net': specifier: workspace:* version: link:../net - '@welshman/relay': - specifier: workspace:* - version: link:../relay '@welshman/router': specifier: workspace:* version: link:../router @@ -241,9 +235,6 @@ importers: '@welshman/lib': specifier: workspace:* version: link:../lib - '@welshman/relay': - specifier: workspace:* - version: link:../relay '@welshman/util': specifier: workspace:* version: link:../util @@ -261,33 +252,14 @@ importers: specifier: ~5.8.0 version: 5.8.2 - packages/relay: - dependencies: - '@welshman/lib': - specifier: workspace:* - version: link:../lib - '@welshman/signer': - specifier: workspace:* - version: link:../signer - '@welshman/util': - specifier: workspace:* - version: link:../util - devDependencies: - rimraf: - specifier: ~6.0.0 - version: 6.0.1 - typescript: - specifier: ~5.8.0 - version: 5.8.2 - packages/router: dependencies: '@welshman/lib': specifier: workspace:* version: link:../lib - '@welshman/relay': + '@welshman/net': specifier: workspace:* - version: link:../relay + version: link:../net '@welshman/util': specifier: workspace:* version: link:../util @@ -338,9 +310,9 @@ importers: '@welshman/lib': specifier: workspace:* version: link:../lib - '@welshman/relay': + '@welshman/net': specifier: workspace:* - version: link:../relay + version: link:../net '@welshman/util': specifier: workspace:* version: link:../util