applesauce-relay 0.0.0-next-20250505205231 → 0.0.0-next-20250526151506

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import * as exports from "../index.js";
3
+ describe("exports", () => {
4
+ it("should export the expected functions", () => {
5
+ expect(Object.keys(exports).sort()).toMatchInlineSnapshot(`
6
+ [
7
+ "Relay",
8
+ "RelayGroup",
9
+ "RelayPool",
10
+ "ReqCloseError",
11
+ "completeOnEose",
12
+ "markFromRelay",
13
+ "onlyEvents",
14
+ "storeEvents",
15
+ "toEventStore",
16
+ ]
17
+ `);
18
+ });
19
+ });
@@ -0,0 +1,10 @@
1
+ import { SimpleSigner } from "applesauce-signers/signers/simple-signer";
2
+ import type { NostrEvent } from "nostr-tools";
3
+ export declare class FakeUser extends SimpleSigner {
4
+ pubkey: string;
5
+ event(data?: Partial<NostrEvent>): NostrEvent;
6
+ note(content?: string, extra?: Partial<NostrEvent>): import("nostr-tools").Event;
7
+ profile(profile?: any, extra?: Partial<NostrEvent>): import("nostr-tools").Event;
8
+ contacts(pubkeys?: string[]): import("nostr-tools").Event;
9
+ list(tags?: string[][], extra?: Partial<NostrEvent>): import("nostr-tools").Event;
10
+ }
@@ -0,0 +1,31 @@
1
+ import { unixNow } from "applesauce-core/helpers";
2
+ import { SimpleSigner } from "applesauce-signers/signers/simple-signer";
3
+ import { finalizeEvent, getPublicKey, kinds } from "nostr-tools";
4
+ export class FakeUser extends SimpleSigner {
5
+ pubkey = getPublicKey(this.key);
6
+ event(data) {
7
+ return finalizeEvent({
8
+ kind: data?.kind ?? kinds.ShortTextNote,
9
+ content: data?.content || "",
10
+ created_at: data?.created_at ?? unixNow(),
11
+ tags: data?.tags || [],
12
+ }, this.key);
13
+ }
14
+ note(content = "Hello World", extra) {
15
+ return this.event({ kind: kinds.ShortTextNote, content, ...extra });
16
+ }
17
+ profile(profile = {}, extra) {
18
+ return this.event({ kind: kinds.Metadata, content: JSON.stringify({ ...profile }), ...extra });
19
+ }
20
+ contacts(pubkeys = []) {
21
+ return this.event({ kind: kinds.Contacts, tags: pubkeys.map((p) => ["p", p]) });
22
+ }
23
+ list(tags = [], extra) {
24
+ return this.event({
25
+ kind: kinds.Bookmarksets,
26
+ content: "",
27
+ tags: [["d", String(Math.round(Math.random() * 10000))], ...tags],
28
+ ...extra,
29
+ });
30
+ }
31
+ }
@@ -4,7 +4,7 @@ import { getSeenRelays } from "applesauce-core/helpers";
4
4
  import { WS } from "vitest-websocket-mock";
5
5
  import { Relay } from "../relay.js";
6
6
  import { filter } from "rxjs/operators";
7
- import { firstValueFrom, of, throwError, timer } from "rxjs";
7
+ import { firstValueFrom, of, Subject, throwError, timer } from "rxjs";
8
8
  const defaultMockInfo = {
9
9
  name: "Test Relay",
10
10
  description: "Test Relay Description",
@@ -144,7 +144,7 @@ describe("req", () => {
144
144
  // Verify the second subscription received the event and EOSE
145
145
  expect(secondSub.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
146
146
  });
147
- it("should wait for authentication if relay info document has limitations.auth_required = true", async () => {
147
+ it("should open connection and wait for authentication if relay info document has limitations.auth_required = true", async () => {
148
148
  // Mock the fetchInformationDocument method to return a document with auth_required = true
149
149
  vi.spyOn(Relay, "fetchInformationDocument").mockImplementation(() => of({
150
150
  name: "Auth Required Relay",
@@ -162,10 +162,20 @@ describe("req", () => {
162
162
  const sub = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
163
163
  // Wait 10ms to ensure the information document is fetched
164
164
  await new Promise((resolve) => setTimeout(resolve, 10));
165
+ // Wait for connection
166
+ await server.connected;
165
167
  // Verify no REQ message was sent yet (waiting for auth)
166
168
  expect(server).not.toHaveReceivedMessages(["REQ", "sub1", { kinds: [1] }]);
169
+ // Send AUTH challenge
170
+ server.send(["AUTH", "challenge"]);
171
+ // Send auth response
172
+ subscribeSpyTo(relay.auth(mockEvent));
173
+ // Verify the auth event was sent
174
+ await expect(server.nextMessage).resolves.toEqual(["AUTH", mockEvent]);
175
+ // Accept auth
176
+ server.send(["OK", mockEvent.id, true, ""]);
167
177
  // Simulate successful authentication
168
- relay.authenticated$.next(true);
178
+ expect(relay.authenticated).toBe(true);
169
179
  // Now the REQ should be sent
170
180
  await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
171
181
  // Send EVENT and EOSE to complete the subscription
@@ -208,6 +218,41 @@ describe("req", () => {
208
218
  relay.ready$.next(true);
209
219
  await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
210
220
  });
221
+ it("should wait for filters if filters are provided as an observable", async () => {
222
+ const filters = new Subject();
223
+ subscribeSpyTo(relay.req(filters, "sub1"));
224
+ // Wait 10sm and ensure no messages were sent yet
225
+ await new Promise((resolve) => setTimeout(resolve, 10));
226
+ expect(server.messagesToConsume.pendingItems.length).toBe(0);
227
+ // Send REQ message with filters
228
+ filters.next([{ kinds: [1] }]);
229
+ // Wait for the REQ message to be sent
230
+ await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
231
+ });
232
+ it("should update filters if filters are provided as an observable", async () => {
233
+ const filters = new Subject();
234
+ subscribeSpyTo(relay.req(filters, "sub1"));
235
+ // Send REQ message with filters
236
+ filters.next([{ kinds: [1] }]);
237
+ // Should send REQ message with new filters
238
+ await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
239
+ // Send REQ message with filters
240
+ filters.next([{ kinds: [2] }]);
241
+ // Should send new REQ message with new filters
242
+ await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [2] }]);
243
+ // It should not send CLOSE message
244
+ await expect(server.messages).not.toContain(["CLOSE", "sub1"]);
245
+ });
246
+ it("should complete if filters are provided as an observable that completes", async () => {
247
+ const filters = new Subject();
248
+ const sub = subscribeSpyTo(relay.req(filters, "sub1"));
249
+ // Send REQ message with filters
250
+ filters.next([{ kinds: [1] }]);
251
+ // Complete the observable
252
+ filters.complete();
253
+ await sub.onComplete();
254
+ expect(sub.receivedComplete()).toBe(true);
255
+ });
211
256
  });
212
257
  describe("event", () => {
213
258
  it("should wait for authentication if relay responds with auth-required", async () => {
package/dist/group.d.ts CHANGED
@@ -1,19 +1,19 @@
1
- import { Filter, NostrEvent } from "nostr-tools";
1
+ import { type NostrEvent } from "nostr-tools";
2
2
  import { Observable } from "rxjs";
3
- import { IGroup, IRelay, PublishResponse, SubscriptionResponse, PublishOptions, RequestOptions, SubscriptionOptions } from "./types.js";
3
+ import { IGroup, IRelay, PublishResponse, SubscriptionResponse, PublishOptions, RequestOptions, SubscriptionOptions, FilterInput } from "./types.js";
4
4
  export declare class RelayGroup implements IGroup {
5
5
  relays: IRelay[];
6
6
  constructor(relays: IRelay[]);
7
7
  /** Takes an array of observables and only emits EOSE when all observables have emitted EOSE */
8
8
  protected mergeEOSE(...requests: Observable<SubscriptionResponse>[]): Observable<import("nostr-tools").Event | "EOSE">;
9
9
  /** Make a request to all relays */
10
- req(filters: Filter | Filter[], id?: string): Observable<SubscriptionResponse>;
10
+ req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
11
11
  /** Send an event to all relays */
12
12
  event(event: NostrEvent): Observable<PublishResponse>;
13
13
  /** Publish an event to all relays with retries ( default 3 retries ) */
14
14
  publish(event: NostrEvent, opts?: PublishOptions): Observable<PublishResponse>;
15
15
  /** Request events from all relays with retries ( default 3 retries ) */
16
- request(filters: Filter | Filter[], opts?: RequestOptions): Observable<NostrEvent>;
16
+ request(filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
17
17
  /** Open a subscription to all relays with retries ( default 3 retries ) */
18
- subscription(filters: Filter | Filter[], opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
18
+ subscription(filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
19
19
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import * as exports from "../index.js";
3
+ describe("exports", () => {
4
+ it("should export the expected functions", () => {
5
+ expect(Object.keys(exports).sort()).toMatchInlineSnapshot(`
6
+ [
7
+ "completeOnEose",
8
+ "markFromRelay",
9
+ "onlyEvents",
10
+ "storeEvents",
11
+ "toEventStore",
12
+ ]
13
+ `);
14
+ });
15
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { EventStore } from "applesauce-core";
2
+ import { describe, expect, it } from "vitest";
3
+ import { FakeUser } from "../../__tests__/fake-user.js";
4
+ import { of } from "rxjs";
5
+ import { subscribeSpyTo } from "@hirez_io/observer-spy";
6
+ import { toEventStore } from "../to-event-store.js";
7
+ const user = new FakeUser();
8
+ describe("toEventStore", () => {
9
+ it("should remove duplicate events", () => {
10
+ const eventStore = new EventStore();
11
+ const event = user.note("original content");
12
+ const source = of(event, { ...event }, { ...event }, { ...event });
13
+ const spy = subscribeSpyTo(source.pipe(toEventStore(eventStore)));
14
+ expect(spy.getValuesLength()).toBe(1);
15
+ expect(spy.getValueAt(0).length).toBe(1);
16
+ expect(spy.getValueAt(0)[0]).toBe(event);
17
+ });
18
+ });
@@ -1,5 +1,5 @@
1
1
  import { IEventStore } from "applesauce-core";
2
2
  import { MonoTypeOperatorFunction } from "rxjs";
3
3
  import { SubscriptionResponse } from "../types.js";
4
- /** Sends all events to the event store */
4
+ /** Sends all events to the event store but does not remove duplicates */
5
5
  export declare function storeEvents(eventStore: IEventStore): MonoTypeOperatorFunction<SubscriptionResponse>;
@@ -1,5 +1,5 @@
1
1
  import { tap } from "rxjs";
2
- /** Sends all events to the event store */
2
+ /** Sends all events to the event store but does not remove duplicates */
3
3
  export function storeEvents(eventStore) {
4
4
  return (source) => {
5
5
  return source.pipe(tap((event) => typeof event !== "string" && eventStore.add(event)));
@@ -2,5 +2,8 @@ import { OperatorFunction } from "rxjs";
2
2
  import { IEventStore } from "applesauce-core";
3
3
  import { NostrEvent } from "nostr-tools";
4
4
  import { SubscriptionResponse } from "../types.js";
5
- /** Adds all events to event store and returns a deduplicated timeline when EOSE is received */
5
+ /**
6
+ * Adds all events to event store and returns a deduplicated timeline when EOSE is received
7
+ * @deprecated use `mapEventsToStore` and `mapEventsToTimeline` instead
8
+ */
6
9
  export declare function toEventStore(eventStore: IEventStore): OperatorFunction<SubscriptionResponse, NostrEvent[]>;
@@ -1,19 +1,15 @@
1
- import { scan } from "rxjs";
2
- import { insertEventIntoDescendingList } from "nostr-tools/utils";
1
+ import { mapEventsToStore, mapEventsToTimeline } from "applesauce-core/observable";
3
2
  import { completeOnEose } from "./complete-on-eose.js";
4
- /** Adds all events to event store and returns a deduplicated timeline when EOSE is received */
3
+ /**
4
+ * Adds all events to event store and returns a deduplicated timeline when EOSE is received
5
+ * @deprecated use `mapEventsToStore` and `mapEventsToTimeline` instead
6
+ */
5
7
  export function toEventStore(eventStore) {
6
8
  return (source) => source.pipe(
7
9
  // Complete when there are not events
8
10
  completeOnEose(),
11
+ // Save events to store and remove duplicates
12
+ mapEventsToStore(eventStore, true),
9
13
  // Add the events to an array
10
- scan((events, event) => {
11
- // Get the current instance of this event
12
- let e = eventStore.add(event);
13
- // If its not in the timeline, add it
14
- if (events.includes(e))
15
- return events;
16
- else
17
- return insertEventIntoDescendingList(events, e);
18
- }, []));
14
+ mapEventsToTimeline());
19
15
  }
package/dist/pool.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { NostrEvent, type Filter } from "nostr-tools";
1
+ import { type NostrEvent } from "nostr-tools";
2
2
  import { BehaviorSubject, Observable } from "rxjs";
3
3
  import { RelayGroup } from "./group.js";
4
4
  import { Relay, RelayOptions } from "./relay.js";
5
- import { IPool, PublishResponse, PublishOptions, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
5
+ import { IPool, PublishResponse, PublishOptions, RequestOptions, SubscriptionOptions, SubscriptionResponse, FilterInput } from "./types.js";
6
6
  export declare class RelayPool implements IPool {
7
7
  options?: RelayOptions | undefined;
8
8
  groups$: BehaviorSubject<Map<string, RelayGroup>>;
@@ -18,13 +18,13 @@ export declare class RelayPool implements IPool {
18
18
  /** Create a group of relays */
19
19
  group(relays: string[]): RelayGroup;
20
20
  /** Make a REQ to multiple relays that does not deduplicate events */
21
- req(relays: string[], filters: Filter | Filter[], id?: string): Observable<SubscriptionResponse>;
21
+ req(relays: string[], filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
22
22
  /** Send an EVENT message to multiple relays */
23
23
  event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
24
24
  /** Publish an event to multiple relays */
25
25
  publish(relays: string[], event: NostrEvent, opts?: PublishOptions): Observable<PublishResponse>;
26
26
  /** Request events from multiple relays */
27
- request(relays: string[], filters: Filter | Filter[], opts?: RequestOptions): Observable<NostrEvent>;
27
+ request(relays: string[], filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
28
28
  /** Open a subscription to multiple relays */
29
- subscription(relays: string[], filters: Filter | Filter[], opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
29
+ subscription(relays: string[], filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
30
30
  }
package/dist/relay.d.ts CHANGED
@@ -3,7 +3,7 @@ import { type Filter, type NostrEvent } from "nostr-tools";
3
3
  import { BehaviorSubject, Observable } from "rxjs";
4
4
  import { WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
5
5
  import { RelayInformation } from "nostr-tools/nip11";
6
- import { AuthSigner, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
6
+ import { AuthSigner, FilterInput, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
7
7
  /** An error that is thrown when a REQ is closed from the relay side */
8
8
  export declare class ReqCloseError extends Error {
9
9
  }
@@ -61,12 +61,12 @@ export declare class Relay implements IRelay {
61
61
  protected authRequiredForReq: Observable<boolean>;
62
62
  protected authRequiredForEvent: Observable<boolean>;
63
63
  protected resetState(): void;
64
- /** An internal observable that is responsible for watching all messages and updating state */
64
+ /** An internal observable that is responsible for watching all messages and updating state, subscribing to it will trigger a connection to the relay */
65
65
  protected watchTower: Observable<never>;
66
66
  constructor(url: string, opts?: RelayOptions);
67
67
  /** Set ready = false and start the reconnect timer */
68
68
  protected startReconnectTimer(error: Error | CloseEvent): void;
69
- /** Wait for ready and authenticated */
69
+ /** Wait for authentication state, make connection and then wait for authentication if required */
70
70
  protected waitForAuth<T extends unknown = unknown>(requireAuth: Observable<boolean>, observable: Observable<T>): Observable<T>;
71
71
  /** Wait for the relay to be ready to accept connections */
72
72
  protected waitForReady<T extends unknown = unknown>(observable: Observable<T>): Observable<T>;
@@ -74,7 +74,7 @@ export declare class Relay implements IRelay {
74
74
  /** Send a message to the relay */
75
75
  next(message: any): void;
76
76
  /** Create a REQ observable that emits events or "EOSE" or errors */
77
- req(filters: Filter | Filter[], id?: string): Observable<SubscriptionResponse>;
77
+ req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
78
78
  /** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
79
79
  event(event: NostrEvent, verb?: "EVENT" | "AUTH"): Observable<PublishResponse>;
80
80
  /** send and AUTH message */
package/dist/relay.js CHANGED
@@ -2,7 +2,7 @@ import { logger } from "applesauce-core";
2
2
  import { simpleTimeout } from "applesauce-core/observable";
3
3
  import { nanoid } from "nanoid";
4
4
  import { nip42 } from "nostr-tools";
5
- import { BehaviorSubject, catchError, combineLatest, defer, filter, from, ignoreElements, map, merge, mergeMap, NEVER, of, retry, scan, share, shareReplay, Subject, switchMap, take, tap, throwError, timeout, timer, } from "rxjs";
5
+ import { BehaviorSubject, catchError, combineLatest, defer, endWith, filter, finalize, from, ignoreElements, isObservable, map, merge, mergeMap, mergeWith, NEVER, of, retry, scan, share, shareReplay, Subject, switchMap, take, takeUntil, tap, throwError, timeout, timer, } from "rxjs";
6
6
  import { webSocket } from "rxjs/webSocket";
7
7
  import { completeOnEose } from "./operators/complete-on-eose.js";
8
8
  import { markFromRelay } from "./operators/mark-from-relay.js";
@@ -85,7 +85,7 @@ export class Relay {
85
85
  if (this.receivedAuthRequiredForEvent.value)
86
86
  this.receivedAuthRequiredForEvent.next(false);
87
87
  }
88
- /** An internal observable that is responsible for watching all messages and updating state */
88
+ /** An internal observable that is responsible for watching all messages and updating state, subscribing to it will trigger a connection to the relay */
89
89
  watchTower;
90
90
  constructor(url, opts) {
91
91
  this.url = url;
@@ -190,11 +190,13 @@ export class Relay {
190
190
  .pipe(take(1))
191
191
  .subscribe(() => this.ready$.next(true));
192
192
  }
193
- /** Wait for ready and authenticated */
193
+ /** Wait for authentication state, make connection and then wait for authentication if required */
194
194
  waitForAuth(
195
195
  // NOTE: require BehaviorSubject so it always has a value
196
196
  requireAuth, observable) {
197
197
  return combineLatest([requireAuth, this.authenticated$]).pipe(
198
+ // Once the auth state is known, make a connection and watch for auth challenges
199
+ mergeWith(this.watchTower),
198
200
  // wait for auth not required or authenticated
199
201
  filter(([required, authenticated]) => !required || authenticated),
200
202
  // complete after the first value so this does not repeat
@@ -225,10 +227,24 @@ export class Relay {
225
227
  }
226
228
  /** Create a REQ observable that emits events or "EOSE" or errors */
227
229
  req(filters, id = nanoid()) {
228
- const request = this.socket.multiplex(() => (Array.isArray(filters) ? ["REQ", id, ...filters] : ["REQ", id, filters]), () => ["CLOSE", id], (message) => (message[0] === "EVENT" || message[0] === "CLOSED" || message[0] === "EOSE") && message[1] === id);
229
- // Start the watch tower with the observable
230
- const withWatchTower = merge(this.watchTower, request);
231
- const observable = withWatchTower.pipe(
230
+ // Convert filters input into an observable, if its a normal value merge it with NEVER so it never completes
231
+ const input = isObservable(filters) ? filters : merge(of(filters), NEVER);
232
+ // Create an observable that completes when the upstream observable completes
233
+ const complete = input.pipe(ignoreElements(), endWith(null));
234
+ // Create an observable that filters responses from the relay to just the ones for this REQ
235
+ const messages = this.socket.pipe(filter((m) => Array.isArray(m) && (m[0] === "EVENT" || m[0] === "CLOSED" || m[0] === "EOSE") && m[1] === id));
236
+ // Create an observable that controls sending the filters and closing the REQ
237
+ const control = input.pipe(
238
+ // Send the filters when they change
239
+ tap((filters) => this.socket.next(Array.isArray(filters) ? ["REQ", id, ...filters] : ["REQ", id, filters])),
240
+ // Close the req when unsubscribed
241
+ finalize(() => this.socket.next(["CLOSE", id])),
242
+ // Once filters have been sent, switch to listening for messages
243
+ switchMap(() => messages));
244
+ // Start the watch tower with the observables
245
+ const observable = merge(this.watchTower, control).pipe(
246
+ // Complete the subscription when the input is completed
247
+ takeUntil(complete),
232
248
  // Map the messages to events, EOSE, or throw an error
233
249
  map((message) => {
234
250
  if (message[0] === "EOSE")
@@ -270,10 +286,8 @@ export class Relay {
270
286
  // format OK message
271
287
  map((m) => ({ ok: m[2], message: m[3], from: this.url })));
272
288
  });
273
- // Start the watch tower with the observable
274
- const withWatchTower = merge(this.watchTower, base);
275
- // Add complete operators
276
- const observable = withWatchTower.pipe(
289
+ // Start the watch tower and add complete operators
290
+ const observable = merge(this.watchTower, base).pipe(
277
291
  // complete on first value
278
292
  take(1),
279
293
  // listen for OK auth-required
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { EventTemplate, Filter, NostrEvent } from "nostr-tools";
1
+ import { type EventTemplate, type Filter, type NostrEvent } from "nostr-tools";
2
2
  import { Observable } from "rxjs";
3
3
  import { WebSocketSubject } from "rxjs/webSocket";
4
4
  export type SubscriptionResponse = NostrEvent | "EOSE";
@@ -28,11 +28,13 @@ export type SubscriptionOptions = {
28
28
  export type AuthSigner = {
29
29
  signEvent: (event: EventTemplate) => NostrEvent | Promise<NostrEvent>;
30
30
  };
31
+ /** The type of input the REQ method accepts */
32
+ export type FilterInput = Filter | Filter[] | Observable<Filter | Filter[]>;
31
33
  export interface Nip01Actions {
32
34
  /** Send an EVENT message */
33
35
  event(event: NostrEvent): Observable<PublishResponse>;
34
36
  /** Send a REQ message */
35
- req(filters: Filter | Filter[], id?: string): Observable<SubscriptionResponse>;
37
+ req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
36
38
  }
37
39
  export interface IRelay extends MultiplexWebSocket, Nip01Actions, IRelayState {
38
40
  url: string;
@@ -52,12 +54,12 @@ export interface IRelay extends MultiplexWebSocket, Nip01Actions, IRelayState {
52
54
  retries?: number;
53
55
  }): Observable<PublishResponse>;
54
56
  /** Send a REQ message with retries */
55
- request(filters: Filter | Filter[], opts?: {
57
+ request(filters: FilterInput, opts?: {
56
58
  id?: string;
57
59
  retries?: number;
58
60
  }): Observable<NostrEvent>;
59
61
  /** Open a subscription with retries */
60
- subscription(filters: Filter | Filter[], opts?: {
62
+ subscription(filters: FilterInput, opts?: {
61
63
  id?: string;
62
64
  retries?: number;
63
65
  }): Observable<SubscriptionResponse>;
@@ -68,12 +70,12 @@ export interface IGroup extends Nip01Actions {
68
70
  retries?: number;
69
71
  }): Observable<PublishResponse>;
70
72
  /** Send a REQ message with retries */
71
- request(filters: Filter | Filter[], opts?: {
73
+ request(filters: FilterInput, opts?: {
72
74
  id?: string;
73
75
  retries?: number;
74
76
  }): Observable<NostrEvent>;
75
77
  /** Open a subscription with retries */
76
- subscription(filters: Filter | Filter[], opts?: {
78
+ subscription(filters: FilterInput, opts?: {
77
79
  id?: string;
78
80
  retries?: number;
79
81
  }): Observable<SubscriptionResponse>;
@@ -82,7 +84,7 @@ export interface IPool {
82
84
  /** Send an EVENT message */
83
85
  event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
84
86
  /** Send a REQ message */
85
- req(relays: string[], filters: Filter | Filter[], id?: string): Observable<SubscriptionResponse>;
87
+ req(relays: string[], filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
86
88
  /** Get or create a relay */
87
89
  relay(url: string): IRelay;
88
90
  /** Create a relay group */
@@ -92,12 +94,12 @@ export interface IPool {
92
94
  retries?: number;
93
95
  }): Observable<PublishResponse>;
94
96
  /** Send a REQ message to relays with retries */
95
- request(relays: string[], filters: Filter | Filter[], opts?: {
97
+ request(relays: string[], filters: FilterInput, opts?: {
96
98
  id?: string;
97
99
  retries?: number;
98
100
  }): Observable<NostrEvent>;
99
101
  /** Open a subscription to relays with retries */
100
- subscription(relays: string[], filters: Filter | Filter[], opts?: {
102
+ subscription(relays: string[], filters: FilterInput, opts?: {
101
103
  id?: string;
102
104
  retries?: number;
103
105
  }): Observable<SubscriptionResponse>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-relay",
3
- "version": "0.0.0-next-20250505205231",
3
+ "version": "0.0.0-next-20250526151506",
4
4
  "description": "nostr relay communication framework built on rxjs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,13 +54,14 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@noble/hashes": "^1.7.1",
57
- "applesauce-core": "^1.0.0",
57
+ "applesauce-core": "0.0.0-next-20250526151506",
58
58
  "nanoid": "^5.0.9",
59
- "nostr-tools": "^2.10.4",
59
+ "nostr-tools": "^2.13",
60
60
  "rxjs": "^7.8.1"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@hirez_io/observer-spy": "^2.2.0",
64
+ "applesauce-signers": "0.0.0-next-20250526151506",
64
65
  "@vitest/expect": "^3.1.1",
65
66
  "typescript": "^5.7.3",
66
67
  "vitest": "^3.1.1",