applesauce-relay 4.1.0 → 4.4.0

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.
package/dist/group.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { Filter, type NostrEvent } from "nostr-tools";
2
- import { Observable } from "rxjs";
1
+ import { type NostrEvent } from "nostr-tools";
2
+ import { BehaviorSubject, MonoTypeOperatorFunction, Observable } from "rxjs";
3
3
  import { IAsyncEventStoreActions, IAsyncEventStoreRead, IEventStoreActions, IEventStoreRead } from "applesauce-core";
4
+ import { type FilterWithAnd } from "applesauce-core/helpers";
4
5
  import { NegentropySyncOptions, type ReconcileFunction } from "./negentropy.js";
5
- import { CountResponse, FilterInput, IGroup, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
6
6
  import { SyncDirection } from "./relay.js";
7
+ import { CountResponse, FilterInput, IGroup, IGroupRelayInput, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
7
8
  /** Options for negentropy sync on a group of relays */
8
9
  export type GroupNegentropySyncOptions = NegentropySyncOptions & {
9
10
  /** Whether to sync in parallel (default true) */
@@ -20,10 +21,21 @@ export type GroupRequestOptions = RequestOptions & {
20
21
  eventStore?: IEventStoreActions | IAsyncEventStoreActions | null;
21
22
  };
22
23
  export declare class RelayGroup implements IGroup {
23
- relays: IRelay[];
24
- constructor(relays: IRelay[]);
25
- /** Takes an array of observables and only emits EOSE when all observables have emitted EOSE */
26
- protected mergeEOSE(requests: Observable<SubscriptionResponse>[], eventStore?: IEventStoreActions | IAsyncEventStoreActions | null): Observable<import("nostr-tools").Event | "EOSE">;
24
+ protected relays$: BehaviorSubject<IRelay[]> | Observable<IRelay[]>;
25
+ get relays(): IRelay[];
26
+ constructor(relays: IGroupRelayInput);
27
+ /** Whether this group is controlled by an upstream observable */
28
+ private get controlled();
29
+ /** Check if a relay is in the group */
30
+ has(relay: IRelay | string): boolean;
31
+ /** Add a relay to the group */
32
+ add(relay: IRelay): void;
33
+ /** Remove a relay from the group */
34
+ remove(relay: IRelay): void;
35
+ /** Internal logic for handling requests to multiple relays */
36
+ protected internalSubscription(project: (relay: IRelay) => Observable<SubscriptionResponse>, eventOperator?: MonoTypeOperatorFunction<NostrEvent>): Observable<SubscriptionResponse>;
37
+ /** Internal logic for handling publishes to multiple relays */
38
+ protected internalPublish(project: (relay: IRelay) => Observable<PublishResponse>): Observable<PublishResponse>;
27
39
  /**
28
40
  * Make a request to all relays
29
41
  * @note This does not deduplicate events
@@ -35,15 +47,15 @@ export declare class RelayGroup implements IGroup {
35
47
  /** Send an event to all relays */
36
48
  event(event: NostrEvent): Observable<PublishResponse>;
37
49
  /** Negentropy sync events with the relays and an event store */
38
- negentropy(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, reconcile: ReconcileFunction, opts?: GroupNegentropySyncOptions): Promise<boolean>;
50
+ negentropy(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, reconcile: ReconcileFunction, opts?: GroupNegentropySyncOptions): Promise<boolean>;
39
51
  /** Publish an event to all relays with retries ( default 3 retries ) */
40
52
  publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse[]>;
41
- /** Request events from all relays with retries ( default 3 retries ) */
53
+ /** Request events from all relays and complete on EOSE */
42
54
  request(filters: FilterInput, opts?: GroupRequestOptions): Observable<NostrEvent>;
43
55
  /** Open a subscription to all relays with retries ( default 3 retries ) */
44
56
  subscription(filters: FilterInput, opts?: GroupSubscriptionOptions): Observable<SubscriptionResponse>;
45
57
  /** Count events on all relays in the group */
46
- count(filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
58
+ count(filters: FilterWithAnd | FilterWithAnd[], id?: string): Observable<Record<string, CountResponse>>;
47
59
  /** Negentropy sync events with the relays and an event store */
48
- sync(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
60
+ sync(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, direction?: SyncDirection): Observable<NostrEvent>;
49
61
  }
package/dist/group.js CHANGED
@@ -1,45 +1,143 @@
1
1
  import { nanoid } from "nanoid";
2
- import { catchError, combineLatest, defer, EMPTY, endWith, identity, ignoreElements, merge, of, share, switchMap, } from "rxjs";
2
+ import { BehaviorSubject, catchError, combineLatest, defaultIfEmpty, defer, endWith, filter, from, identity, ignoreElements, lastValueFrom, map, merge, of, scan, share, switchMap, take, takeWhile, toArray, } from "rxjs";
3
3
  import { EventMemory, filterDuplicateEvents, } from "applesauce-core";
4
4
  import { completeOnEose } from "./operators/complete-on-eose.js";
5
5
  import { onlyEvents } from "./operators/only-events.js";
6
+ import { reverseSwitchMap } from "./operators/reverse-switch-map.js";
7
+ /** Convert an error to a PublishResponse */
8
+ function errorToPublishResponse(relay) {
9
+ return catchError((err) => of({ ok: false, from: relay.url, message: err?.message || "Unknown error" }));
10
+ }
6
11
  export class RelayGroup {
7
- relays;
12
+ relays$ = new BehaviorSubject([]);
13
+ get relays() {
14
+ if (this.relays$ instanceof BehaviorSubject)
15
+ return this.relays$.value;
16
+ throw new Error("This group was created with an observable, relays are not available");
17
+ }
8
18
  constructor(relays) {
9
- this.relays = relays;
19
+ this.relays$ = Array.isArray(relays) ? new BehaviorSubject(relays) : relays;
20
+ }
21
+ /** Whether this group is controlled by an upstream observable */
22
+ get controlled() {
23
+ return this.relays$ instanceof BehaviorSubject === false;
24
+ }
25
+ /** Check if a relay is in the group */
26
+ has(relay) {
27
+ if (this.controlled)
28
+ throw new Error("This group was created with an observable, relays are not available");
29
+ if (typeof relay === "string")
30
+ return this.relays.some((r) => r.url === relay);
31
+ return this.relays.includes(relay);
10
32
  }
11
- /** Takes an array of observables and only emits EOSE when all observables have emitted EOSE */
12
- mergeEOSE(requests, eventStore = new EventMemory()) {
13
- // Create stream of events only
14
- const events = merge(...requests).pipe(
15
- // Ignore non event responses
33
+ /** Add a relay to the group */
34
+ add(relay) {
35
+ if (this.has(relay))
36
+ return;
37
+ this.relays$.next([...this.relays, relay]);
38
+ }
39
+ /** Remove a relay from the group */
40
+ remove(relay) {
41
+ if (!this.has(relay))
42
+ return;
43
+ this.relays$.next(this.relays.filter((r) => r !== relay));
44
+ }
45
+ /** Internal logic for handling requests to multiple relays */
46
+ internalSubscription(project, eventOperator = identity) {
47
+ // Keep a cache of upstream observables for each relay
48
+ const upstream = new WeakMap();
49
+ // Subscribe to the group relays
50
+ const main = this.relays$.pipe(
51
+ // Every time they change switch to a new observable
52
+ // Using reverseSwitchMap to subscribe to the new relays before unsubscribing from the old ones
53
+ // This avoids sending duplicate REQ messages to the relays
54
+ reverseSwitchMap((relays) => {
55
+ const observables = [];
56
+ for (const relay of relays) {
57
+ // If an upstream observable exists for this relay, use it
58
+ if (upstream.has(relay)) {
59
+ observables.push(upstream.get(relay));
60
+ continue;
61
+ }
62
+ const observable = project(relay).pipe(
63
+ // Catch connection errors and return EOSE
64
+ catchError(() => of("EOSE")),
65
+ // Map values into tuple of relay and value
66
+ map((value) => [relay, value]));
67
+ observables.push(observable);
68
+ upstream.set(relay, observable);
69
+ }
70
+ return merge(...observables);
71
+ }),
72
+ // Only create one upstream subscription
73
+ share());
74
+ // Create an observable that only emits the events from the relays
75
+ const events = main.pipe(
76
+ // Pick the value from the tuple
77
+ map(([_, value]) => value),
78
+ // Only return events
16
79
  onlyEvents(),
17
- // If an event store is provided, filter duplicate events
18
- eventStore ? filterDuplicateEvents(eventStore) : identity);
19
- // Create stream that emits EOSE when all relays have sent EOSE
20
- const eose = merge(
21
- // Create a new map of requests that only emits EOSE
22
- ...requests.map((observable) => observable.pipe(completeOnEose(), ignoreElements()))).pipe(
80
+ // Add event operations
81
+ eventOperator);
82
+ // Create an observable that emits EOSE when all relays have sent EOSE
83
+ const eose = this.relays$.pipe(
84
+ // When the relays change, switch to a new observable
85
+ switchMap((relays) =>
86
+ // Subscribe to the events, and wait for EOSE from all relays
87
+ main.pipe(
88
+ // Only select EOSE messages
89
+ filter(([_, value]) => value === "EOSE"),
90
+ // Track the relays that have sent EOSE
91
+ scan((received, [relay]) => [...received, relay], []),
92
+ // Keep the observable open while there are relays that have not sent EOSE
93
+ takeWhile((received) => relays.some((r) => !received.includes(r))),
94
+ // Ignore all values
95
+ ignoreElements(),
23
96
  // When all relays have sent EOSE, emit EOSE
24
- endWith("EOSE"));
25
- return merge(events, eose);
97
+ endWith("EOSE"))));
98
+ return merge(events, eose).pipe(
99
+ // Ensure a single upstream
100
+ share());
101
+ }
102
+ /** Internal logic for handling publishes to multiple relays */
103
+ internalPublish(project) {
104
+ // Keep a cache of upstream observables for each relay
105
+ const upstream = new WeakMap();
106
+ // Subscribe to the group relays
107
+ return this.relays$.pipe(
108
+ // Take a snapshot of relays (no updates yet...)
109
+ take(1),
110
+ // Every time they change switch to a new observable
111
+ switchMap((relays) => {
112
+ const observables = [];
113
+ for (const relay of relays) {
114
+ // If an upstream observable exists for this relay, use it
115
+ if (upstream.has(relay)) {
116
+ observables.push(upstream.get(relay));
117
+ continue;
118
+ }
119
+ // Create a new upstream observable for this relay
120
+ const observable = project(relay).pipe(
121
+ // Catch error and return as PublishResponse
122
+ errorToPublishResponse(relay));
123
+ observables.push(observable);
124
+ upstream.set(relay, observable);
125
+ }
126
+ return merge(...observables);
127
+ }),
128
+ // Ensure a single upstream
129
+ share());
26
130
  }
27
131
  /**
28
132
  * Make a request to all relays
29
133
  * @note This does not deduplicate events
30
134
  */
31
135
  req(filters, id = nanoid(), opts) {
32
- const requests = this.relays.map((relay) => relay.req(filters, id).pipe(
33
- // Ignore connection errors
34
- catchError(() => of("EOSE"))));
35
- // Merge events and the single EOSE stream
36
- return this.mergeEOSE(requests, opts?.eventStore);
136
+ return this.internalSubscription((relay) => relay.req(filters, id), opts?.eventStore ? filterDuplicateEvents(opts?.eventStore) : identity);
37
137
  }
38
138
  /** Send an event to all relays */
39
139
  event(event) {
40
- return merge(...this.relays.map((relay) => relay.event(event).pipe(
41
- // Catch error and return as PublishResponse
42
- catchError((err) => of({ ok: false, from: relay.url, message: err?.message || "Unknown error" })))));
140
+ return this.internalPublish((relay) => relay.event(event));
43
141
  }
44
142
  /** Negentropy sync events with the relays and an event store */
45
143
  async negentropy(store, filter, reconcile, opts) {
@@ -57,29 +155,29 @@ export class RelayGroup {
57
155
  }
58
156
  /** Publish an event to all relays with retries ( default 3 retries ) */
59
157
  publish(event, opts) {
60
- return Promise.all(this.relays.map((relay) => relay.publish(event, opts).catch(
61
- // Catch error and return as PublishResponse
62
- (err) => ({ ok: false, from: relay.url, message: err?.message || "Unknown error" }))));
158
+ return lastValueFrom(this.internalPublish((relay) => from(relay.publish(event, opts))).pipe(toArray(), defaultIfEmpty([])));
63
159
  }
64
- /** Request events from all relays with retries ( default 3 retries ) */
160
+ /** Request events from all relays and complete on EOSE */
65
161
  request(filters, opts) {
66
- return merge(...this.relays.map((relay) => relay.request(filters, opts).pipe(
67
- // Ignore individual connection errors
68
- catchError(() => EMPTY)))).pipe(
162
+ return this.internalSubscription((relay) => relay.request(filters, opts).pipe(
163
+ // Simulate EOSE on completion
164
+ endWith("EOSE")),
69
165
  // If an event store is provided, filter duplicate events
70
- opts?.eventStore == null ? identity : filterDuplicateEvents(opts?.eventStore ?? new EventMemory()));
166
+ opts?.eventStore == null ? identity : filterDuplicateEvents(opts?.eventStore ?? new EventMemory())).pipe(
167
+ // Complete when all relays have sent EOSE
168
+ completeOnEose());
71
169
  }
72
170
  /** Open a subscription to all relays with retries ( default 3 retries ) */
73
171
  subscription(filters, opts) {
74
- return this.mergeEOSE(this.relays.map((relay) => relay.subscription(filters, opts).pipe(
75
- // Ignore individual connection errors
76
- catchError(() => EMPTY))),
77
- // Pass event store so that duplicate events are removed
78
- opts?.eventStore);
172
+ return this.internalSubscription((relay) => relay.subscription(filters, opts),
173
+ // If an event store is provided, filter duplicate events
174
+ opts?.eventStore == null ? identity : filterDuplicateEvents(opts?.eventStore ?? new EventMemory()));
79
175
  }
80
176
  /** Count events on all relays in the group */
81
177
  count(filters, id = nanoid()) {
82
- return combineLatest(Object.fromEntries(this.relays.map((relay) => [relay.url, relay.count(filters, id)])));
178
+ return this.relays$.pipe(switchMap((relays) => combineLatest(Object.fromEntries(relays.map((relay) => [relay.url, relay.count(filters, id)])))),
179
+ // Ensure a single upstream
180
+ share());
83
181
  }
84
182
  /** Negentropy sync events with the relays and an event store */
85
183
  sync(store, filter, direction) {
@@ -1,7 +1,7 @@
1
1
  import { IAsyncEventStoreRead, IEventStoreRead } from "applesauce-core";
2
- import { Filter } from "nostr-tools";
3
- import { MultiplexWebSocket } from "./types.js";
2
+ import { type FilterWithAnd } from "applesauce-core/helpers";
4
3
  import { NegentropyStorageVector } from "./lib/negentropy.js";
4
+ import { MultiplexWebSocket } from "./types.js";
5
5
  /**
6
6
  * A function that reconciles the storage vectors with a remote relay
7
7
  * @param have - The ids that the local storage has
@@ -15,7 +15,7 @@ export type NegentropySyncOptions = {
15
15
  signal?: AbortSignal;
16
16
  };
17
17
  /** Creates a NegentropyStorageVector from an event store and filter */
18
- export declare function buildStorageFromFilter(store: IEventStoreRead | IAsyncEventStoreRead, filter: Filter): Promise<NegentropyStorageVector>;
18
+ export declare function buildStorageFromFilter(store: IEventStoreRead | IAsyncEventStoreRead, filter: FilterWithAnd): Promise<NegentropyStorageVector>;
19
19
  /** Creates a NegentropyStorageVector from an array of items */
20
20
  export declare function buildStorageVector(items: {
21
21
  id: string;
@@ -28,4 +28,4 @@ export declare function buildStorageVector(items: {
28
28
  */
29
29
  export declare function negentropySync(storage: NegentropyStorageVector, socket: MultiplexWebSocket & {
30
30
  next: (msg: any) => void;
31
- }, filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
31
+ }, filter: FilterWithAnd, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
@@ -1,6 +1,6 @@
1
1
  import { logger } from "applesauce-core";
2
- import { map, share, firstValueFrom } from "rxjs";
3
2
  import { nanoid } from "nanoid";
3
+ import { firstValueFrom, map, Observable, race, share } from "rxjs";
4
4
  import { Negentropy, NegentropyStorageVector } from "./lib/negentropy.js";
5
5
  const log = logger.extend("negentropy");
6
6
  /** Creates a NegentropyStorageVector from an event store and filter */
@@ -52,16 +52,49 @@ export async function negentropySync(storage, socket, filter, reconcile, opts) {
52
52
  throw new Error(msg[2]);
53
53
  return msg[2];
54
54
  }), share());
55
+ // Check if already aborted before starting sync
56
+ if (opts?.signal?.aborted)
57
+ return false;
58
+ // Create an observable that emits when abort signal is triggered
59
+ const abortSignal$ = new Observable((observer) => {
60
+ if (opts?.signal?.aborted) {
61
+ observer.next("abort");
62
+ observer.complete();
63
+ return;
64
+ }
65
+ const onAbort = () => {
66
+ observer.next("abort");
67
+ observer.complete();
68
+ };
69
+ opts?.signal?.addEventListener("abort", onAbort);
70
+ return () => opts?.signal?.removeEventListener("abort", onAbort);
71
+ });
55
72
  // keep an additional subscription open while waiting for async operations
56
- const sub = incoming.subscribe((m) => log(m));
73
+ const sub = incoming.subscribe({
74
+ next: (m) => log(m),
75
+ error: () => { }, // Ignore errors here, they'll be caught by firstValueFrom
76
+ });
57
77
  try {
58
78
  while (msg && opts?.signal?.aborted !== true) {
59
- const received = await firstValueFrom(incoming);
60
- if (opts?.signal?.aborted)
61
- return false;
62
- const [newMsg, have, need] = await ne.reconcile(received);
63
- await reconcile(have, need);
64
- msg = newMsg;
79
+ // Race between incoming message and abort signal
80
+ try {
81
+ const received = await firstValueFrom(race(incoming.pipe(map((m) => ({ type: "message", data: m }))), abortSignal$.pipe(map(() => ({ type: "abort" })))));
82
+ if (received.type === "abort" || opts?.signal?.aborted) {
83
+ sub.unsubscribe();
84
+ return false;
85
+ }
86
+ const [newMsg, have, need] = await ne.reconcile(received.data);
87
+ await reconcile(have, need);
88
+ msg = newMsg;
89
+ }
90
+ catch (err) {
91
+ // Check if aborted during reconcile or message processing
92
+ if (opts?.signal?.aborted) {
93
+ sub.unsubscribe();
94
+ return false;
95
+ }
96
+ throw err;
97
+ }
65
98
  }
66
99
  }
67
100
  catch (err) {
@@ -1,6 +1,7 @@
1
1
  export * from "./complete-on-eose.js";
2
+ export * from "./liveness.js";
2
3
  export * from "./mark-from-relay.js";
3
4
  export * from "./only-events.js";
5
+ export * from "./reverse-switch-map.js";
4
6
  export * from "./store-events.js";
5
7
  export * from "./to-event-store.js";
6
- export * from "./liveness.js";
@@ -1,6 +1,7 @@
1
1
  export * from "./complete-on-eose.js";
2
+ export * from "./liveness.js";
2
3
  export * from "./mark-from-relay.js";
3
4
  export * from "./only-events.js";
5
+ export * from "./reverse-switch-map.js";
4
6
  export * from "./store-events.js";
5
7
  export * from "./to-event-store.js";
6
- export * from "./liveness.js";
@@ -0,0 +1,9 @@
1
+ import { OperatorFunction, ObservableInput, ObservedValueOf } from "rxjs";
2
+ /**
3
+ * Like switchMap, but subscribes to the new observable before unsubscribing from the old one.
4
+ * This prevents gaps in subscription coverage.
5
+ *
6
+ * @param project A function that, when applied to an item emitted by the source Observable,
7
+ * returns an Observable.
8
+ */
9
+ export declare function reverseSwitchMap<T, O extends ObservableInput<any>>(project: (value: T, index: number) => O): OperatorFunction<T, ObservedValueOf<O>>;
@@ -0,0 +1,46 @@
1
+ import { from } from "rxjs";
2
+ import { createOperatorSubscriber } from "rxjs/internal/operators/OperatorSubscriber";
3
+ import { operate } from "rxjs/internal/util/lift";
4
+ /**
5
+ * Like switchMap, but subscribes to the new observable before unsubscribing from the old one.
6
+ * This prevents gaps in subscription coverage.
7
+ *
8
+ * @param project A function that, when applied to an item emitted by the source Observable,
9
+ * returns an Observable.
10
+ */
11
+ export function reverseSwitchMap(project) {
12
+ return operate((source, subscriber) => {
13
+ let innerSubscriber = null;
14
+ let index = 0;
15
+ // Whether or not the source subscription has completed
16
+ let isComplete = false;
17
+ // We only complete the result if the source is complete AND we don't have an active inner subscription.
18
+ // This is called both when the source completes and when the inners complete.
19
+ const checkComplete = () => {
20
+ if (isComplete && !innerSubscriber)
21
+ subscriber.complete();
22
+ };
23
+ source.subscribe(createOperatorSubscriber(subscriber, (value) => {
24
+ const outerIndex = index++;
25
+ const oldSubscriber = innerSubscriber;
26
+ // Create the new inner subscription FIRST
27
+ // Immediately assign the new subscriber because observables can emit and complete synchronously
28
+ const self = (innerSubscriber = createOperatorSubscriber(subscriber, (innerValue) => subscriber.next(innerValue), () => {
29
+ // The inner has completed. Null out the inner subscriber to
30
+ // free up memory and to signal that we have no inner subscription
31
+ // currently. Only do this if this is still the active inner subscriber.
32
+ if (innerSubscriber === self || innerSubscriber === null) {
33
+ innerSubscriber = null;
34
+ checkComplete();
35
+ }
36
+ }));
37
+ // Subscribe to the new observable FIRST
38
+ from(project(value, outerIndex)).subscribe(innerSubscriber);
39
+ // THEN unsubscribe from the previous inner subscription
40
+ oldSubscriber?.unsubscribe();
41
+ }, () => {
42
+ isComplete = true;
43
+ checkComplete();
44
+ }));
45
+ });
46
+ }
package/dist/pool.d.ts CHANGED
@@ -1,44 +1,44 @@
1
1
  import type { IAsyncEventStoreRead, IEventStoreRead } from "applesauce-core";
2
+ import { FilterMap, OutboxMap, type FilterWithAnd } from "applesauce-core/helpers";
2
3
  import { Filter, type NostrEvent } from "nostr-tools";
3
4
  import { BehaviorSubject, Observable, Subject } from "rxjs";
4
5
  import { RelayGroup } from "./group.js";
5
6
  import type { NegentropySyncOptions, ReconcileFunction } from "./negentropy.js";
6
7
  import { Relay, SyncDirection, type RelayOptions } from "./relay.js";
7
- import type { CountResponse, FilterInput, IPool, IRelay, PublishResponse, SubscriptionResponse } from "./types.js";
8
+ import type { CountResponse, FilterInput, IPool, IPoolRelayInput, IRelay, PublishResponse, SubscriptionResponse } from "./types.js";
8
9
  export declare class RelayPool implements IPool {
9
10
  options?: RelayOptions | undefined;
10
- groups$: BehaviorSubject<Map<string, RelayGroup>>;
11
- get groups(): Map<string, RelayGroup>;
12
11
  relays$: BehaviorSubject<Map<string, Relay>>;
13
12
  get relays(): Map<string, Relay>;
14
13
  /** A signal when a relay is added */
15
14
  add$: Subject<IRelay>;
16
15
  /** A signal when a relay is removed */
17
16
  remove$: Subject<IRelay>;
18
- /** An array of relays to never connect to */
19
- blacklist: Set<string>;
20
17
  constructor(options?: RelayOptions | undefined);
21
- protected filterBlacklist(urls: string[]): string[];
22
18
  /** Get or create a new relay connection */
23
19
  relay(url: string): Relay;
24
20
  /** Create a group of relays */
25
- group(relays: string[]): RelayGroup;
21
+ group(relays: IPoolRelayInput): RelayGroup;
26
22
  /** Removes a relay from the pool and defaults to closing the connection */
27
23
  remove(relay: string | IRelay, close?: boolean): void;
28
24
  /** Make a REQ to multiple relays that does not deduplicate events */
29
- req(relays: string[], filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
25
+ req(relays: IPoolRelayInput, filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
30
26
  /** Send an EVENT message to multiple relays */
31
- event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
27
+ event(relays: IPoolRelayInput, event: NostrEvent): Observable<PublishResponse>;
32
28
  /** Negentropy sync event ids with the relays and an event store */
33
- negentropy(relays: string[], store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
29
+ negentropy(relays: IPoolRelayInput, store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
34
30
  /** Publish an event to multiple relays */
35
- publish(relays: string[], event: Parameters<RelayGroup["publish"]>[0], opts?: Parameters<RelayGroup["publish"]>[1]): Promise<PublishResponse[]>;
31
+ publish(relays: IPoolRelayInput, event: Parameters<RelayGroup["publish"]>[0], opts?: Parameters<RelayGroup["publish"]>[1]): Promise<PublishResponse[]>;
36
32
  /** Request events from multiple relays */
37
- request(relays: string[], filters: Parameters<RelayGroup["request"]>[0], opts?: Parameters<RelayGroup["request"]>[1]): Observable<NostrEvent>;
33
+ request(relays: IPoolRelayInput, filters: FilterInput, opts?: Parameters<RelayGroup["request"]>[1]): Observable<NostrEvent>;
38
34
  /** Open a subscription to multiple relays */
39
- subscription(relays: string[], filters: Parameters<RelayGroup["subscription"]>[0], options?: Parameters<RelayGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
35
+ subscription(relays: IPoolRelayInput, filters: FilterInput, options?: Parameters<RelayGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
36
+ /** Open a subscription for a map of relays and filters */
37
+ subscriptionMap(relays: FilterMap | Observable<FilterMap>, options?: Parameters<RelayGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
38
+ /** Open a subscription for an {@link OutboxMap} and filter */
39
+ outboxSubscription(outboxes: OutboxMap | Observable<OutboxMap>, filter: Omit<Filter, "authors">, options?: Parameters<RelayGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
40
40
  /** Count events on multiple relays */
41
- count(relays: string[], filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
41
+ count(relays: IPoolRelayInput, filters: FilterWithAnd | FilterWithAnd[], id?: string): Observable<Record<string, CountResponse>>;
42
42
  /** Negentropy sync events with the relays and an event store */
43
- sync(relays: string[], store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
43
+ sync(relays: IPoolRelayInput, store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, direction?: SyncDirection): Observable<NostrEvent>;
44
44
  }
package/dist/pool.js CHANGED
@@ -1,13 +1,9 @@
1
- import { normalizeURL } from "applesauce-core/helpers";
2
- import { BehaviorSubject, Subject } from "rxjs";
1
+ import { createFilterMap, isFilterEqual, normalizeURL, } from "applesauce-core/helpers";
2
+ import { BehaviorSubject, distinctUntilChanged, isObservable, map, of, Subject } from "rxjs";
3
3
  import { RelayGroup } from "./group.js";
4
4
  import { Relay } from "./relay.js";
5
5
  export class RelayPool {
6
6
  options;
7
- groups$ = new BehaviorSubject(new Map());
8
- get groups() {
9
- return this.groups$.value;
10
- }
11
7
  relays$ = new BehaviorSubject(new Map());
12
8
  get relays() {
13
9
  return this.relays$.value;
@@ -16,34 +12,13 @@ export class RelayPool {
16
12
  add$ = new Subject();
17
13
  /** A signal when a relay is removed */
18
14
  remove$ = new Subject();
19
- /** An array of relays to never connect to */
20
- blacklist = new Set();
21
15
  constructor(options) {
22
16
  this.options = options;
23
- // Listen for relays being added and removed to emit connect / disconnect signals
24
- // const listeners = new Map<IRelay, Subscription>();
25
- // this.add$.subscribe((relay) =>
26
- // listeners.set(
27
- // relay,
28
- // relay.connected$.subscribe((conn) => (conn ? this.connect$.next(relay) : this.disconnect$.next(relay))),
29
- // ),
30
- // );
31
- // this.remove$.subscribe((relay) => {
32
- // const listener = listeners.get(relay);
33
- // if (listener) listener.unsubscribe();
34
- // listeners.delete(relay);
35
- // });
36
- }
37
- filterBlacklist(urls) {
38
- return urls.filter((url) => !this.blacklist.has(url));
39
17
  }
40
18
  /** Get or create a new relay connection */
41
19
  relay(url) {
42
20
  // Normalize the url
43
21
  url = normalizeURL(url);
44
- // Check if the url is blacklisted
45
- if (this.blacklist.has(url))
46
- throw new Error("Relay is on blacklist");
47
22
  // Check if the relay already exists
48
23
  let relay = this.relays.get(url);
49
24
  if (relay)
@@ -57,17 +32,9 @@ export class RelayPool {
57
32
  }
58
33
  /** Create a group of relays */
59
34
  group(relays) {
60
- // Normalize all urls
61
- relays = relays.map((url) => normalizeURL(url));
62
- // Filter out any blacklisted relays
63
- relays = this.filterBlacklist(relays);
64
- const key = relays.sort().join(",");
65
- let group = this.groups.get(key);
66
- if (group)
67
- return group;
68
- group = new RelayGroup(relays.map((url) => this.relay(url)));
69
- this.groups$.next(this.groups.set(key, group));
70
- return group;
35
+ return new RelayGroup(Array.isArray(relays)
36
+ ? relays.map((url) => this.relay(url))
37
+ : relays.pipe(map((urls) => urls.map((url) => this.relay(url)))));
71
38
  }
72
39
  /** Removes a relay from the pool and defaults to closing the connection */
73
40
  remove(relay, close = true) {
@@ -112,6 +79,30 @@ export class RelayPool {
112
79
  subscription(relays, filters, options) {
113
80
  return this.group(relays).subscription(filters, options);
114
81
  }
82
+ /** Open a subscription for a map of relays and filters */
83
+ subscriptionMap(relays, options) {
84
+ // Convert input to observable
85
+ const relays$ = isObservable(relays) ? relays : of(relays);
86
+ return this.group(
87
+ // Create a group with an observable of dynamic relay urls
88
+ relays$.pipe(map((dir) => Object.keys(dir)))).subscription((relay) => {
89
+ // Return observable to subscribe to the relays unique filters
90
+ return relays$.pipe(
91
+ // Select the relays filters
92
+ map((dir) => dir[relay.url]),
93
+ // Don't send duplicate filters
94
+ distinctUntilChanged(isFilterEqual));
95
+ }, options);
96
+ }
97
+ /** Open a subscription for an {@link OutboxMap} and filter */
98
+ outboxSubscription(outboxes, filter, options) {
99
+ const filterMap = isObservable(outboxes)
100
+ ? outboxes.pipe(
101
+ // Project outbox map to filter map
102
+ map((outboxes) => createFilterMap(outboxes, filter)))
103
+ : createFilterMap(outboxes, filter);
104
+ return this.subscriptionMap(filterMap, options);
105
+ }
115
106
  /** Count events on multiple relays */
116
107
  count(relays, filters, id) {
117
108
  return this.group(relays).count(filters, id);
package/dist/relay.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { IAsyncEventStoreRead, IEventStoreRead, logger } from "applesauce-core";
2
- import { type Filter, type NostrEvent } from "nostr-tools";
2
+ import { type FilterWithAnd } from "applesauce-core/helpers";
3
+ import { type NostrEvent } from "nostr-tools";
3
4
  import { RelayInformation } from "nostr-tools/nip11";
4
5
  import { BehaviorSubject, MonoTypeOperatorFunction, Observable, RepeatConfig, RetryConfig, Subject } from "rxjs";
5
6
  import { WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
@@ -105,13 +106,13 @@ export declare class Relay implements IRelay {
105
106
  /** Create a REQ observable that emits events or "EOSE" or errors */
106
107
  req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
107
108
  /** Create a COUNT observable that emits a single count response */
108
- count(filters: Filter | Filter[], id?: string): Observable<CountResponse>;
109
+ count(filters: FilterWithAnd | FilterWithAnd[], id?: string): Observable<CountResponse>;
109
110
  /** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
110
111
  event(event: NostrEvent, verb?: "EVENT" | "AUTH"): Observable<PublishResponse>;
111
112
  /** send and AUTH message */
112
113
  auth(event: NostrEvent): Promise<PublishResponse>;
113
114
  /** Negentropy sync event ids with the relay and an event store */
114
- negentropy(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
115
+ negentropy(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
115
116
  /** Authenticate with the relay using a signer */
116
117
  authenticate(signer: AuthSigner): Promise<PublishResponse>;
117
118
  /** Internal operator for creating the retry() operator */
@@ -123,13 +124,13 @@ export declare class Relay implements IRelay {
123
124
  /** Internal operator for handling auth-required errors from REQ/COUNT operations */
124
125
  protected handleAuthRequiredForReq(operation: "REQ" | "COUNT"): MonoTypeOperatorFunction<any>;
125
126
  /** Creates a REQ that retries when relay errors ( default 3 retries ) */
126
- subscription(filters: Filter | Filter[], opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
127
+ subscription(filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
127
128
  /** Makes a single request that retires on errors and completes on EOSE */
128
- request(filters: Filter | Filter[], opts?: RequestOptions): Observable<NostrEvent>;
129
+ request(filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
129
130
  /** Publishes an event to the relay and retries when relay errors or responds with auth-required ( default 3 retries ) */
130
131
  publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse>;
131
132
  /** Negentropy sync events with the relay and an event store */
132
- sync(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
133
+ sync(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, direction?: SyncDirection): Observable<NostrEvent>;
133
134
  /** Force close the connection */
134
135
  close(): void;
135
136
  /** An async method that returns the NIP-11 information document for the relay */
package/dist/relay.js CHANGED
@@ -265,7 +265,15 @@ export class Relay {
265
265
  /** Create a REQ observable that emits events or "EOSE" or errors */
266
266
  req(filters, id = nanoid()) {
267
267
  // Convert filters input into an observable, if its a normal value merge it with NEVER so it never completes
268
- const input = isObservable(filters) ? filters : merge(of(filters), NEVER);
268
+ let input;
269
+ // Create input from filters input
270
+ if (typeof filters === "function") {
271
+ const result = filters(this);
272
+ input = (isObservable(result) ? result : merge(of(result), NEVER)).pipe(map((f) => (Array.isArray(f) ? f : [f])));
273
+ }
274
+ else {
275
+ input = (isObservable(filters) ? filters : merge(of(filters), NEVER)).pipe(map((f) => (Array.isArray(f) ? f : [f])));
276
+ }
269
277
  // Create an observable that completes when the upstream observable completes
270
278
  const filtersComplete = input.pipe(ignoreElements(), endWith(null));
271
279
  // Create an observable that filters responses from the relay to just the ones for this REQ
@@ -275,7 +283,7 @@ export class Relay {
275
283
  // Create an observable that controls sending the filters and closing the REQ
276
284
  const control = input.pipe(
277
285
  // Send the filters when they change
278
- tap((filters) => this.socket.next(Array.isArray(filters) ? ["REQ", id, ...filters] : ["REQ", id, filters])),
286
+ tap((filters) => this.socket.next(["REQ", id, ...filters])),
279
287
  // Send the CLOSE message when unsubscribed or input completes
280
288
  finalize(() => this.socket.next(["CLOSE", id])),
281
289
  // Once filters have been sent, switch to listening for messages
@@ -498,6 +506,14 @@ export class Relay {
498
506
  };
499
507
  return new Observable((observer) => {
500
508
  const controller = new AbortController();
509
+ let cleanupCalled = false;
510
+ // Store reference to cleanup the negentropy properly
511
+ const cleanup = () => {
512
+ if (!cleanupCalled) {
513
+ cleanupCalled = true;
514
+ controller.abort();
515
+ }
516
+ };
501
517
  this.negentropy(store, filter, async (have, need) => {
502
518
  // NOTE: it may be more efficient to sync all the events later in a single batch
503
519
  // Send missing events to the relay
@@ -512,11 +528,17 @@ export class Relay {
512
528
  }
513
529
  }, { signal: controller.signal })
514
530
  // Complete the observable when the sync is complete
515
- .then(() => observer.complete())
531
+ .then(() => {
532
+ if (!cleanupCalled)
533
+ observer.complete();
534
+ })
516
535
  // Error the observable when the sync fails
517
- .catch((err) => observer.error(err));
536
+ .catch((err) => {
537
+ if (!cleanupCalled)
538
+ observer.error(err);
539
+ });
518
540
  // Cancel the sync when the observable is unsubscribed
519
- return () => controller.abort();
541
+ return cleanup;
520
542
  }).pipe(
521
543
  // Only create one upstream subscription
522
544
  share());
package/dist/types.d.ts CHANGED
@@ -1,11 +1,12 @@
1
- import type { EventTemplate, Filter, NostrEvent } from "nostr-tools";
1
+ import type { IAsyncEventStoreRead, IEventStoreRead } from "applesauce-core";
2
+ import type { FilterWithAnd } from "applesauce-core/helpers";
3
+ import type { EventTemplate, NostrEvent } from "nostr-tools";
2
4
  import type { RelayInformation } from "nostr-tools/nip11";
3
5
  import type { Observable, repeat, retry } from "rxjs";
4
6
  import type { WebSocketSubject } from "rxjs/webSocket";
7
+ import type { GroupNegentropySyncOptions, GroupRequestOptions, GroupSubscriptionOptions } from "./group.js";
5
8
  import type { NegentropySyncOptions, ReconcileFunction } from "./negentropy.js";
6
- import type { IAsyncEventStoreRead, IEventStoreRead } from "applesauce-core";
7
9
  import type { SyncDirection } from "./relay.js";
8
- import type { GroupNegentropySyncOptions, GroupRequestOptions, GroupSubscriptionOptions } from "./group.js";
9
10
  export type SubscriptionResponse = NostrEvent | "EOSE";
10
11
  export type PublishResponse = {
11
12
  ok: boolean;
@@ -58,8 +59,8 @@ export type SubscriptionOptions = {
58
59
  export type AuthSigner = {
59
60
  signEvent: (event: EventTemplate) => NostrEvent | Promise<NostrEvent>;
60
61
  };
61
- /** The type of input the REQ method accepts */
62
- export type FilterInput = Filter | Filter[] | Observable<Filter | Filter[]>;
62
+ /** Filters that can be passed to request methods on the pool or relay */
63
+ export type FilterInput = FilterWithAnd | FilterWithAnd[] | Observable<FilterWithAnd | FilterWithAnd[]> | ((relay: IRelay) => FilterWithAnd | FilterWithAnd[] | Observable<FilterWithAnd | FilterWithAnd[]>);
63
64
  export interface IRelay extends MultiplexWebSocket {
64
65
  url: string;
65
66
  message$: Observable<any>;
@@ -81,13 +82,13 @@ export interface IRelay extends MultiplexWebSocket {
81
82
  /** Send a REQ message */
82
83
  req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
83
84
  /** Send a COUNT message */
84
- count(filters: Filter | Filter[], id?: string): Observable<CountResponse>;
85
+ count(filters: FilterWithAnd | FilterWithAnd[], id?: string): Observable<CountResponse>;
85
86
  /** Send an EVENT message */
86
87
  event(event: NostrEvent): Observable<PublishResponse>;
87
88
  /** Send an AUTH message */
88
89
  auth(event: NostrEvent): Promise<PublishResponse>;
89
90
  /** Negentropy sync event ids with the relay and an event store */
90
- negentropy(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
91
+ negentropy(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
91
92
  /** Authenticate with the relay using a signer */
92
93
  authenticate(signer: AuthSigner): Promise<PublishResponse>;
93
94
  /** Send an EVENT message with retries */
@@ -97,7 +98,7 @@ export interface IRelay extends MultiplexWebSocket {
97
98
  /** Open a subscription with retries */
98
99
  subscription(filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
99
100
  /** Negentropy sync events with the relay and an event store */
100
- sync(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
101
+ sync(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, direction?: SyncDirection): Observable<NostrEvent>;
101
102
  /** Get the NIP-11 information document for the relay */
102
103
  getInformation(): Promise<RelayInformation | null>;
103
104
  /** Get the limitations for the relay */
@@ -105,50 +106,58 @@ export interface IRelay extends MultiplexWebSocket {
105
106
  /** Get the supported NIPs for the relay */
106
107
  getSupported(): Promise<number[] | null>;
107
108
  }
109
+ export type IGroupRelayInput = IRelay[] | Observable<IRelay[]>;
108
110
  export interface IGroup {
109
111
  /** Send a REQ message */
110
- req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
112
+ req(filters: Parameters<IRelay["req"]>[0], id?: string): Observable<SubscriptionResponse>;
111
113
  /** Send an EVENT message */
112
- event(event: NostrEvent): Observable<PublishResponse>;
114
+ event(event: Parameters<IRelay["event"]>[0]): Observable<PublishResponse>;
113
115
  /** Negentropy sync event ids with the relays and an event store */
114
- negentropy(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
116
+ negentropy(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
117
+ /** Add a relay to the group */
118
+ add(relay: IRelay): void;
119
+ /** Remove a relay from the group */
120
+ remove(relay: IRelay): void;
121
+ /** Check if a relay is in the group */
122
+ has(relay: IRelay | string): boolean;
115
123
  /** Send an EVENT message with retries */
116
- publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse[]>;
124
+ publish(event: Parameters<IRelay["event"]>[0], opts?: PublishOptions): Promise<PublishResponse[]>;
117
125
  /** Send a REQ message with retries */
118
- request(filters: FilterInput, opts?: GroupRequestOptions): Observable<NostrEvent>;
126
+ request(filters: Parameters<IRelay["request"]>[0], opts?: GroupRequestOptions): Observable<NostrEvent>;
119
127
  /** Open a subscription with retries */
120
- subscription(filters: FilterInput, opts?: GroupSubscriptionOptions): Observable<SubscriptionResponse>;
128
+ subscription(filters: Parameters<IRelay["subscription"]>[0], opts?: GroupSubscriptionOptions): Observable<SubscriptionResponse>;
121
129
  /** Count events on the relays and an event store */
122
- count(filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
130
+ count(filters: FilterWithAnd | FilterWithAnd[], id?: string): Observable<Record<string, CountResponse>>;
123
131
  /** Negentropy sync events with the relay and an event store */
124
- sync(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
132
+ sync(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, direction?: SyncDirection): Observable<NostrEvent>;
125
133
  }
126
134
  /** Signals emitted by the pool */
127
135
  export interface IPoolSignals {
128
136
  add$: Observable<IRelay>;
129
137
  remove$: Observable<IRelay>;
130
138
  }
139
+ export type IPoolRelayInput = string[] | Observable<string[]>;
131
140
  export interface IPool extends IPoolSignals {
132
141
  /** Get or create a relay */
133
142
  relay(url: string): IRelay;
134
143
  /** Create a relay group */
135
- group(relays: string[]): IGroup;
144
+ group(relays: IPoolRelayInput): IGroup;
136
145
  /** Removes a relay from the pool and defaults to closing the connection */
137
146
  remove(relay: string | IRelay, close?: boolean): void;
138
147
  /** Send a REQ message */
139
- req(relays: string[], filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
148
+ req(relays: IPoolRelayInput, filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
140
149
  /** Send an EVENT message */
141
- event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
150
+ event(relays: IPoolRelayInput, event: NostrEvent): Observable<PublishResponse>;
142
151
  /** Negentropy sync event ids with the relays and an event store */
143
- negentropy(relays: string[], store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, reconcile: ReconcileFunction, opts?: GroupNegentropySyncOptions): Promise<boolean>;
152
+ negentropy(relays: IPoolRelayInput, store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, reconcile: ReconcileFunction, opts?: GroupNegentropySyncOptions): Promise<boolean>;
144
153
  /** Send an EVENT message to relays with retries */
145
- publish(relays: string[], event: Parameters<IGroup["publish"]>[0], opts?: Parameters<IGroup["publish"]>[1]): Promise<PublishResponse[]>;
154
+ publish(relays: IPoolRelayInput, event: Parameters<IGroup["publish"]>[0], opts?: Parameters<IGroup["publish"]>[1]): Promise<PublishResponse[]>;
146
155
  /** Send a REQ message to relays with retries */
147
- request(relays: string[], filters: Parameters<IGroup["request"]>[0], opts?: Parameters<IGroup["request"]>[1]): Observable<NostrEvent>;
156
+ request(relays: IPoolRelayInput, filters: Parameters<IGroup["request"]>[0], opts?: Parameters<IGroup["request"]>[1]): Observable<NostrEvent>;
148
157
  /** Open a subscription to relays with retries */
149
- subscription(relays: string[], filters: Parameters<IGroup["subscription"]>[0], opts?: Parameters<IGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
158
+ subscription(relays: IPoolRelayInput, filters: Parameters<IGroup["subscription"]>[0], opts?: Parameters<IGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
150
159
  /** Count events on the relays and an event store */
151
- count(relays: string[], filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
160
+ count(relays: IPoolRelayInput, filters: FilterWithAnd | FilterWithAnd[], id?: string): Observable<Record<string, CountResponse>>;
152
161
  /** Negentropy sync events with the relay and an event store */
153
- sync(relays: string[], store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
162
+ sync(relays: IPoolRelayInput, store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: FilterWithAnd, direction?: SyncDirection): Observable<NostrEvent>;
154
163
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-relay",
3
- "version": "4.1.0",
3
+ "version": "4.4.0",
4
4
  "description": "nostr relay communication framework built on rxjs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,14 +52,14 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@noble/hashes": "^1.7.1",
55
- "applesauce-core": "^4.1.0",
55
+ "applesauce-core": "^4.4.0",
56
56
  "nanoid": "^5.0.9",
57
57
  "nostr-tools": "~2.17",
58
58
  "rxjs": "^7.8.1"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@hirez_io/observer-spy": "^2.2.0",
62
- "applesauce-signers": "^4.1.0",
62
+ "applesauce-signers": "^4.2.0",
63
63
  "rimraf": "^6.0.1",
64
64
  "typescript": "^5.7.3",
65
65
  "vitest": "^3.2.4",