applesauce-relay 5.1.0 → 6.0.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,62 +1,51 @@
1
- import { IAsyncEventStoreActions, IEventStoreActions } from "applesauce-core/event-store";
2
1
  import type { Filter, NostrEvent } from "applesauce-core/helpers";
3
- import { BehaviorSubject, MonoTypeOperatorFunction, Observable } from "rxjs";
4
- import { NegentropySyncOptions, type ReconcileFunction } from "./negentropy.js";
5
- import { SyncDirection } from "./relay.js";
6
- import { CountResponse, FilterInput, IGroup, IGroupRelayInput, IRelay, NegentropyReadStore, NegentropySyncStore, PublishOptions, PublishResponse, RelayStatus, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
7
- /** Options for negentropy sync on a group of relays */
8
- export type GroupNegentropySyncOptions = NegentropySyncOptions & {
9
- /** Whether to sync in parallel (default true) */
10
- parallel?: boolean;
11
- };
12
- /** Options for a subscription on a group of relays */
13
- export type GroupSubscriptionOptions = SubscriptionOptions & {
14
- /** Deduplicate events with an event store (default is a temporary instance of EventMemory), null will disable deduplication */
15
- eventStore?: IEventStoreActions | IAsyncEventStoreActions | null;
16
- };
17
- /** Options for a request on a group of relays */
18
- export type GroupRequestOptions = RequestOptions & {
19
- /** Deduplicate events with an event store (default is a temporary instance of EventMemory), null will disable deduplication */
20
- eventStore?: IEventStoreActions | IAsyncEventStoreActions | null;
21
- };
22
- export declare class RelayGroup implements IGroup {
23
- protected relays$: BehaviorSubject<IRelay[]> | Observable<IRelay[]>;
2
+ import { BehaviorSubject, Observable } from "rxjs";
3
+ import { type ReconcileFunction } from "./negentropy.js";
4
+ import { Relay, SyncDirection } from "./relay.js";
5
+ import { FilterInput, GroupNegentropySyncOptions, GroupRelayInput, GroupReqMessage, GroupReqOptions, GroupRequestCompleteOperator, GroupRequestOptions, GroupSubscriptionOptions, NegentropyReadStore, NegentropySyncStore, PublishOptions, PublishResponse, RelayCountResponse, RelayReqMessage, RelayStatus } from "./types.js";
6
+ export declare class RelayGroup {
7
+ protected relays$: BehaviorSubject<Relay[]> | Observable<Relay[]>;
24
8
  /** Observable of relay status for all relays in the group */
25
9
  status$: Observable<Record<string, RelayStatus>>;
26
- get relays(): IRelay[];
27
- constructor(relays: IGroupRelayInput);
10
+ get relays(): Relay[];
11
+ constructor(relays: GroupRelayInput);
28
12
  /** Whether this group is controlled by an upstream observable */
29
13
  private get controlled();
30
14
  /** Check if a relay is in the group */
31
- has(relay: IRelay | string): boolean;
15
+ has(relay: Relay | string): boolean;
32
16
  /** Add a relay to the group */
33
- add(relay: IRelay): void;
17
+ add(relay: Relay): void;
34
18
  /** Remove a relay from the group */
35
- remove(relay: IRelay): void;
19
+ remove(relay: Relay): void;
36
20
  /** Internal logic for handling requests to multiple relays */
37
- protected internalSubscription(project: (relay: IRelay) => Observable<SubscriptionResponse>, eventOperator?: MonoTypeOperatorFunction<NostrEvent>): Observable<SubscriptionResponse>;
21
+ protected internalSubscription(project: (relay: Relay) => Observable<RelayReqMessage>): Observable<GroupReqMessage>;
38
22
  /** Internal logic for handling publishes to multiple relays */
39
- protected internalPublish(project: (relay: IRelay) => Observable<PublishResponse>): Observable<PublishResponse>;
40
- /**
41
- * Make a request to all relays
42
- * @note This does not deduplicate events
43
- */
44
- req(filters: FilterInput, id?: string, opts?: {
45
- /** Deduplicate events with an event store (default is a temporary instance of EventMemory), null will disable deduplication */
46
- eventStore?: IEventStoreActions | IAsyncEventStoreActions | null;
47
- }): Observable<SubscriptionResponse>;
23
+ protected internalPublish(project: (relay: Relay) => Observable<PublishResponse>): Observable<PublishResponse>;
24
+ /** Send a REQ to all relays and returns all responses */
25
+ req(filters: FilterInput, opts?: GroupReqOptions): Observable<GroupReqMessage>;
48
26
  /** Send an event to all relays */
49
27
  event(event: NostrEvent): Observable<PublishResponse>;
50
28
  /** Negentropy sync events with the relays and an event store */
51
29
  negentropy(store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: GroupNegentropySyncOptions): Promise<boolean>;
52
30
  /** Publish an event to all relays with retries ( default 3 retries ) */
53
31
  publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse[]>;
54
- /** Request events from all relays and complete on EOSE */
32
+ /** Request events from all relays and complete based on condition */
55
33
  request(filters: FilterInput, opts?: GroupRequestOptions): Observable<NostrEvent>;
56
34
  /** Open a subscription to all relays with retries ( default 3 retries ) */
57
- subscription(filters: FilterInput, opts?: GroupSubscriptionOptions): Observable<SubscriptionResponse>;
35
+ subscription(filters: FilterInput, opts?: GroupSubscriptionOptions): Observable<NostrEvent>;
58
36
  /** Count events on all relays in the group */
59
- count(filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
37
+ count(filters: Filter | Filter[], id?: string): Observable<Record<string, RelayCountResponse>>;
60
38
  /** Negentropy sync events with the relays and an event store */
61
39
  sync(store: NegentropySyncStore | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
40
+ /**
41
+ * Creates a complete condition that waits for the first EOSE message from a relay and then starts a timeout for the remaining relays
42
+ * @param timeout - The timeout in milliseconds for the remaining relays
43
+ */
44
+ static completeAfterFirstRelay(timeout?: number): GroupRequestCompleteOperator;
45
+ /** Creates a group request complete operator that waits for all relays to send an EOSE message or timeout */
46
+ static completeOnAllEose(): GroupRequestCompleteOperator;
47
+ /** A group request complete condition that completes when any of the provided conditions are truthy (OR) */
48
+ static completeOnAny(...conditions: GroupRequestCompleteOperator[]): GroupRequestCompleteOperator;
49
+ /** A group request complete condition that completes when any of the provided conditions are truthy (AND) */
50
+ static completeOnAll(...conditions: GroupRequestCompleteOperator[]): GroupRequestCompleteOperator;
62
51
  }
package/dist/group.js CHANGED
@@ -1,9 +1,8 @@
1
1
  import { EventMemory } from "applesauce-core/event-store";
2
2
  import { filterDuplicateEvents } from "applesauce-core/observable";
3
3
  import { nanoid } from "nanoid";
4
- import { BehaviorSubject, catchError, combineLatest, defaultIfEmpty, defer, endWith, filter, from, identity, ignoreElements, lastValueFrom, map, merge, of, scan, share, shareReplay, startWith, switchMap, take, takeWhile, toArray, } from "rxjs";
5
- import { completeOnEose } from "./operators/complete-on-eose.js";
6
- import { onlyEvents } from "./operators/only-events.js";
4
+ import { BehaviorSubject, catchError, combineLatest, connect, defaultIfEmpty, defer, filter, from, identity, lastValueFrom, map, merge, of, scan, share, shareReplay, startWith, switchMap, take, timeout, timer, toArray, } from "rxjs";
5
+ import { completeWhen } from "./operators/complete-when.js";
7
6
  import { reverseSwitchMap } from "./operators/reverse-switch-map.js";
8
7
  /** Convert an error to a PublishResponse */
9
8
  function errorToPublishResponse(relay) {
@@ -63,11 +62,11 @@ export class RelayGroup {
63
62
  this.relays$.next(this.relays.filter((r) => r !== relay));
64
63
  }
65
64
  /** Internal logic for handling requests to multiple relays */
66
- internalSubscription(project, eventOperator = identity) {
65
+ internalSubscription(project) {
67
66
  // Keep a cache of upstream observables for each relay
68
67
  const upstream = new WeakMap();
69
68
  // Subscribe to the group relays
70
- const main = this.relays$.pipe(
69
+ const messages = this.relays$.pipe(
71
70
  // Every time they change switch to a new observable
72
71
  // Using reverseSwitchMap to subscribe to the new relays before unsubscribing from the old ones
73
72
  // This avoids sending duplicate REQ messages to the relays
@@ -80,44 +79,17 @@ export class RelayGroup {
80
79
  continue;
81
80
  }
82
81
  const observable = project(relay).pipe(
83
- // Catch connection errors and return EOSE
84
- catchError(() => of("EOSE")),
85
- // Map values into tuple of relay and value
86
- map((value) => [relay, value]));
82
+ // Catch connection errors and return ERROR
83
+ catchError((err) => of({ type: "ERROR", from: relay.url, error: err })));
87
84
  observables.push(observable);
88
85
  upstream.set(relay, observable);
89
86
  }
90
87
  return merge(...observables);
91
88
  }),
92
- // Only create one upstream subscription
93
- share());
94
- // Create an observable that only emits the events from the relays
95
- const events = main.pipe(
96
- // Pick the value from the tuple
97
- map(([_, value]) => value),
98
- // Only return events
99
- onlyEvents(),
100
- // Add event operations
101
- eventOperator);
102
- // Create an observable that emits EOSE when all relays have sent EOSE
103
- const eose = this.relays$.pipe(
104
- // When the relays change, switch to a new observable
105
- switchMap((relays) =>
106
- // Subscribe to the events, and wait for EOSE from all relays
107
- main.pipe(
108
- // Only select EOSE messages
109
- filter(([_, value]) => value === "EOSE"),
110
- // Track the relays that have sent EOSE
111
- scan((received, [relay]) => [...received, relay], []),
112
- // Keep the observable open while there are relays that have not sent EOSE
113
- takeWhile((received) => relays.some((r) => !received.includes(r))),
114
- // Ignore all values
115
- ignoreElements(),
116
- // When all relays have sent EOSE, emit EOSE
117
- endWith("EOSE"))));
118
- return merge(events, eose).pipe(
119
- // Ensure a single upstream
89
+ // Ensure a single upstream subscription
90
+ // NOTE: this is required because the complete operator will subscribe many times to this
120
91
  share());
92
+ return messages;
121
93
  }
122
94
  /** Internal logic for handling publishes to multiple relays */
123
95
  internalPublish(project) {
@@ -144,20 +116,17 @@ export class RelayGroup {
144
116
  upstream.set(relay, observable);
145
117
  }
146
118
  return merge(...observables);
147
- }));
119
+ }),
120
+ // Ensure a single upstream publish
121
+ share());
148
122
  }
149
- /**
150
- * Make a request to all relays
151
- * @note This does not deduplicate events
152
- */
153
- req(filters, id = nanoid(), opts) {
154
- return this.internalSubscription((relay) => relay.req(filters, id), opts?.eventStore ? filterDuplicateEvents(opts?.eventStore) : identity);
123
+ /** Send a REQ to all relays and returns all responses */
124
+ req(filters, opts) {
125
+ return this.internalSubscription((relay) => relay.req(filters, opts));
155
126
  }
156
127
  /** Send an event to all relays */
157
128
  event(event) {
158
- return this.internalPublish((relay) => relay.event(event)).pipe(
159
- // Ensure a single upstream subscription
160
- share());
129
+ return this.internalPublish((relay) => relay.event(event));
161
130
  }
162
131
  /** Negentropy sync events with the relays and an event store */
163
132
  async negentropy(store, filter, reconcile, opts) {
@@ -177,21 +146,42 @@ export class RelayGroup {
177
146
  publish(event, opts) {
178
147
  return lastValueFrom(this.internalPublish((relay) => from(relay.publish(event, opts))).pipe(toArray(), defaultIfEmpty([])));
179
148
  }
180
- /** Request events from all relays and complete on EOSE */
149
+ /** Request events from all relays and complete based on condition */
181
150
  request(filters, opts) {
182
- return this.internalSubscription((relay) => relay.request(filters, opts).pipe(
183
- // Simulate EOSE on completion
184
- endWith("EOSE")),
151
+ const complete = opts?.complete ??
152
+ // Default complete condition is to wait for the first relay to send an EOSE message and then timeout or all relays to EOSE
153
+ RelayGroup.completeOnAny(RelayGroup.completeAfterFirstRelay(5_000), RelayGroup.completeOnAllEose());
154
+ return this.internalSubscription(
155
+ // NOTE: we need to use the .req() method here because it returns the full RelayReqResponse object
156
+ (relay) => relay.req(filters,
157
+ // Manually default to relays reconnect config
158
+ { ...opts, reconnect: opts?.reconnect ?? relay.requestReconnect })).pipe(
159
+ // Add the completion condition if provided
160
+ complete ? completeWhen(complete) : identity,
161
+ // Add request timeout
162
+ timeout({ first: opts?.timeout ?? 30_000 }),
163
+ // Filter only for event messages
164
+ filter((message) => message.type === "EVENT"),
165
+ // Extract event messages
166
+ map((message) => message.event),
185
167
  // If an event store is provided, filter duplicate events
186
- opts?.eventStore == null ? identity : filterDuplicateEvents(opts?.eventStore ?? new EventMemory())).pipe(
187
- // Complete when all relays have sent EOSE
188
- completeOnEose());
168
+ opts?.eventStore === null ? identity : filterDuplicateEvents(opts?.eventStore ?? new EventMemory()),
169
+ // Only create one upstream subscription
170
+ share());
189
171
  }
190
172
  /** Open a subscription to all relays with retries ( default 3 retries ) */
191
173
  subscription(filters, opts) {
192
- return this.internalSubscription((relay) => relay.subscription(filters, opts),
174
+ return this.internalSubscription(
175
+ // NOTE: we need to use the .req() method here because it returns the full RelayReqResponse object
176
+ (relay) => relay.req(filters, { ...opts, reconnect: opts?.reconnect ?? relay.subscriptionReconnect })).pipe(
177
+ // Filter only for event messages
178
+ filter((message) => message.type === "EVENT"),
179
+ // Extract event messages
180
+ map((message) => message.event),
193
181
  // If an event store is provided, filter duplicate events
194
- opts?.eventStore == null ? identity : filterDuplicateEvents(opts?.eventStore ?? new EventMemory()));
182
+ opts?.eventStore === null ? identity : filterDuplicateEvents(opts?.eventStore ?? new EventMemory()),
183
+ // Only create one upstream subscription
184
+ share());
195
185
  }
196
186
  /** Count events on all relays in the group */
197
187
  count(filters, id = nanoid()) {
@@ -214,4 +204,43 @@ export class RelayGroup {
214
204
  // Only create one upstream subscription
215
205
  share());
216
206
  }
207
+ /**
208
+ * Creates a complete condition that waits for the first EOSE message from a relay and then starts a timeout for the remaining relays
209
+ * @param timeout - The timeout in milliseconds for the remaining relays
210
+ */
211
+ static completeAfterFirstRelay(timeout = 5_000) {
212
+ return (source) =>
213
+ // Listen for first EOSE message from a relay
214
+ source.pipe(filter((m) => m.type === "EOSE")).pipe(
215
+ // Ignore all other EOSE messages
216
+ take(1),
217
+ // Start a timeout for the remaining relays
218
+ switchMap(() => timer(timeout)),
219
+ // Emit true when the timeout completes
220
+ map(() => true));
221
+ }
222
+ /** Creates a group request complete operator that waits for all relays to send an EOSE message or timeout */
223
+ static completeOnAllEose() {
224
+ return (source) => source.pipe(
225
+ // Filter for relay status messages
226
+ filter((message) => message.type === "OPEN" || message.type === "EOSE" || message.type === "ERROR"),
227
+ // Accumulate the relay status messages
228
+ scan((acc, message) => acc.set(message.from, message.type), new Map()),
229
+ // Emit true when all relays are no longer OPEN
230
+ map((all) => Array.from(all.values()).every((t) => t !== "OPEN")));
231
+ }
232
+ /** A group request complete condition that completes when any of the provided conditions are truthy (OR) */
233
+ static completeOnAny(...conditions) {
234
+ return connect((shared$) =>
235
+ // Merge all the conditions
236
+ merge(...conditions.map((condition) => shared$.pipe(condition))));
237
+ }
238
+ /** A group request complete condition that completes when any of the provided conditions are truthy (AND) */
239
+ static completeOnAll(...conditions) {
240
+ return connect((shared$) =>
241
+ // Get last state from all conditions
242
+ combineLatest(conditions.map((condition) => shared$.pipe(condition))).pipe(
243
+ // Check all values are truthy
244
+ map((all) => all.every((v) => !!v))));
245
+ }
217
246
  }
@@ -1,5 +1,5 @@
1
1
  import { Observable } from "rxjs";
2
- import { IPool } from "./types.js";
2
+ import type { RelayPool } from "./pool.js";
3
3
  /** Relay health states for liveness tracking */
4
4
  export type RelayHealthState = "online" | "offline" | "dead";
5
5
  /** State information for a relay's health tracking */
@@ -114,9 +114,9 @@ export declare class RelayLiveness {
114
114
  reset(relay?: string): void;
115
115
  private connections;
116
116
  /** Connect to a {@link RelayPool} instance and track relay connections */
117
- connectToPool(pool: IPool): void;
117
+ connectToPool(pool: RelayPool): void;
118
118
  /** Disconnect from a {@link RelayPool} instance */
119
- disconnectFromPool(pool: IPool): void;
119
+ disconnectFromPool(pool: RelayPool): void;
120
120
  private updateRelayState;
121
121
  private saveKnownRelays;
122
122
  private saveRelayState;
@@ -1,6 +1,6 @@
1
1
  import { MonoTypeOperatorFunction, OperatorFunction } from "rxjs";
2
2
  import { NostrEvent } from "applesauce-core/helpers/event";
3
- import { SubscriptionResponse } from "../types.js";
4
- export declare function completeOnEose(includeEose: true): MonoTypeOperatorFunction<SubscriptionResponse>;
5
- export declare function completeOnEose(): OperatorFunction<SubscriptionResponse, NostrEvent>;
6
- export declare function completeOnEose(includeEose: false): OperatorFunction<SubscriptionResponse, NostrEvent>;
3
+ import { RelaySubscriptionResponse } from "../types.js";
4
+ export declare function completeOnEose(includeEose: true): MonoTypeOperatorFunction<RelaySubscriptionResponse>;
5
+ export declare function completeOnEose(): OperatorFunction<RelaySubscriptionResponse, NostrEvent>;
6
+ export declare function completeOnEose(includeEose: false): OperatorFunction<RelaySubscriptionResponse, NostrEvent>;
@@ -0,0 +1,7 @@
1
+ import { MonoTypeOperatorFunction, OperatorFunction } from "rxjs";
2
+ /**
3
+ * Complete an observable when an operator emits a value
4
+ * @param operator - The operator to apply to the source observable
5
+ * @param check - A method used to check value for completion, defaults to truthy
6
+ */
7
+ export declare function completeWhen<T, U>(operator: OperatorFunction<T, U>, check?: ((v: U) => boolean) | null): MonoTypeOperatorFunction<T>;
@@ -0,0 +1,14 @@
1
+ import { connect, filter, take, takeUntil } from "rxjs";
2
+ /**
3
+ * Complete an observable when an operator emits a value
4
+ * @param operator - The operator to apply to the source observable
5
+ * @param check - A method used to check value for completion, defaults to truthy
6
+ */
7
+ export function completeWhen(operator, check = (v) => !!v) {
8
+ return connect((shared$) => {
9
+ const complete$ = check ? shared$.pipe(operator, filter(check), take(1)) : shared$.pipe(operator, take(1));
10
+ return shared$.pipe(
11
+ // Complete when the operator returns truthy value
12
+ takeUntil(complete$));
13
+ });
14
+ }
@@ -1,7 +1,5 @@
1
1
  export * from "./complete-on-eose.js";
2
2
  export * from "./liveness.js";
3
- export * from "./mark-from-relay.js";
4
3
  export * from "./only-events.js";
5
4
  export * from "./reverse-switch-map.js";
6
5
  export * from "./store-events.js";
7
- export * from "./to-event-store.js";
@@ -1,7 +1,5 @@
1
1
  export * from "./complete-on-eose.js";
2
2
  export * from "./liveness.js";
3
- export * from "./mark-from-relay.js";
4
3
  export * from "./only-events.js";
5
4
  export * from "./reverse-switch-map.js";
6
5
  export * from "./store-events.js";
7
- export * from "./to-event-store.js";
@@ -1,5 +1,5 @@
1
1
  import { OperatorFunction } from "rxjs";
2
2
  import { NostrEvent } from "applesauce-core/helpers/event";
3
- import { SubscriptionResponse } from "../types.js";
3
+ import { RelaySubscriptionResponse } from "../types.js";
4
4
  /** Filter subscription responses and only return the events */
5
- export declare function onlyEvents(): OperatorFunction<SubscriptionResponse, NostrEvent>;
5
+ export declare function onlyEvents(): OperatorFunction<RelaySubscriptionResponse, NostrEvent>;
@@ -1,5 +1,5 @@
1
1
  import { IEventStore } from "applesauce-core";
2
2
  import { MonoTypeOperatorFunction } from "rxjs";
3
- import { SubscriptionResponse } from "../types.js";
3
+ import { RelaySubscriptionResponse } from "../types.js";
4
4
  /** Sends all events to the event store but does not remove duplicates */
5
- export declare function storeEvents(eventStore: IEventStore): MonoTypeOperatorFunction<SubscriptionResponse>;
5
+ export declare function storeEvents(eventStore: IEventStore): MonoTypeOperatorFunction<RelaySubscriptionResponse>;
package/dist/pool.d.ts CHANGED
@@ -5,8 +5,8 @@ import { BehaviorSubject, Observable, Subject } from "rxjs";
5
5
  import { RelayGroup } from "./group.js";
6
6
  import type { NegentropySyncOptions, ReconcileFunction } from "./negentropy.js";
7
7
  import { Relay, SyncDirection, type RelayOptions } from "./relay.js";
8
- import type { CountResponse, FilterInput, IPool, IPoolRelayInput, IRelay, NegentropyReadStore, NegentropySyncStore, PublishResponse, RelayStatus, SubscriptionResponse } from "./types.js";
9
- export declare class RelayPool implements IPool {
8
+ import type { RelayCountResponse, FilterInput, GroupReqOptions, GroupReqMessage, PoolRelayInput, NegentropyReadStore, NegentropySyncStore, PublishResponse, RelayStatus } from "./types.js";
9
+ export declare class RelayPool {
10
10
  options?: RelayOptions | undefined;
11
11
  relays$: BehaviorSubject<Map<string, Relay>>;
12
12
  get relays(): Map<string, Relay>;
@@ -15,34 +15,34 @@ export declare class RelayPool implements IPool {
15
15
  /** Whether to ignore relays that are ready=false */
16
16
  ignoreOffline: boolean;
17
17
  /** A signal when a relay is added */
18
- add$: Subject<IRelay>;
18
+ add$: Subject<Relay>;
19
19
  /** A signal when a relay is removed */
20
- remove$: Subject<IRelay>;
20
+ remove$: Subject<Relay>;
21
21
  constructor(options?: RelayOptions | undefined);
22
22
  /** Get or create a new relay connection */
23
23
  relay(url: string): Relay;
24
24
  /** Create a group of relays */
25
- group(relays: IPoolRelayInput, ignoreOffline?: boolean): RelayGroup;
25
+ group(relays: PoolRelayInput, ignoreOffline?: boolean): RelayGroup;
26
26
  /** Removes a relay from the pool and defaults to closing the connection */
27
- remove(relay: string | IRelay, close?: boolean): void;
27
+ remove(relay: string | Relay, close?: boolean): void;
28
28
  /** Make a REQ to multiple relays that does not deduplicate events */
29
- req(relays: IPoolRelayInput, filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
29
+ req(relays: PoolRelayInput, filters: FilterInput, opts?: GroupReqOptions): Observable<GroupReqMessage>;
30
30
  /** Send an EVENT message to multiple relays */
31
- event(relays: IPoolRelayInput, event: NostrEvent): Observable<PublishResponse>;
31
+ event(relays: PoolRelayInput, event: NostrEvent): Observable<PublishResponse>;
32
32
  /** Negentropy sync event ids with the relays and an event store */
33
- negentropy(relays: IPoolRelayInput, store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
33
+ negentropy(relays: PoolRelayInput, store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
34
34
  /** Publish an event to multiple relays */
35
- publish(relays: IPoolRelayInput, event: Parameters<RelayGroup["publish"]>[0], opts?: Parameters<RelayGroup["publish"]>[1]): Promise<PublishResponse[]>;
35
+ publish(relays: PoolRelayInput, event: Parameters<RelayGroup["publish"]>[0], opts?: Parameters<RelayGroup["publish"]>[1]): Promise<PublishResponse[]>;
36
36
  /** Request events from multiple relays */
37
- request(relays: IPoolRelayInput, filters: FilterInput, opts?: Parameters<RelayGroup["request"]>[1]): Observable<NostrEvent>;
37
+ request(relays: PoolRelayInput, filters: Parameters<RelayGroup["request"]>[0], opts?: Parameters<RelayGroup["request"]>[1]): Observable<NostrEvent>;
38
38
  /** Open a subscription to multiple relays */
39
- subscription(relays: IPoolRelayInput, filters: FilterInput, options?: Parameters<RelayGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
39
+ subscription(relays: PoolRelayInput, filters: Parameters<RelayGroup["subscription"]>[0], options?: Parameters<RelayGroup["subscription"]>[1]): Observable<NostrEvent>;
40
40
  /** Open a subscription for a map of relays and filters */
41
- subscriptionMap(relays: FilterMap | Observable<FilterMap>, options?: Parameters<RelayGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
41
+ subscriptionMap(relays: FilterMap | Observable<FilterMap>, options?: Parameters<RelayGroup["subscription"]>[1]): Observable<NostrEvent>;
42
42
  /** Open a subscription for an {@link OutboxMap} and filter */
43
- outboxSubscription(outboxes: OutboxMap | Observable<OutboxMap>, filter: Omit<Filter, "authors">, options?: Parameters<RelayGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
43
+ outboxSubscription(outboxes: OutboxMap | Observable<OutboxMap>, filter: Omit<Filter, "authors">, options?: Parameters<RelayGroup["subscription"]>[1]): Observable<NostrEvent>;
44
44
  /** Count events on multiple relays */
45
- count(relays: IPoolRelayInput, filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
45
+ count(relays: PoolRelayInput, filters: Filter | Filter[], id?: string): Observable<Record<string, RelayCountResponse>>;
46
46
  /** Negentropy sync events with the relays and an event store */
47
- sync(relays: IPoolRelayInput, store: NegentropySyncStore | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
47
+ sync(relays: PoolRelayInput, store: NegentropySyncStore | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
48
48
  }
package/dist/pool.js CHANGED
@@ -82,9 +82,9 @@ export class RelayPool {
82
82
  this.remove$.next(instance);
83
83
  }
84
84
  /** Make a REQ to multiple relays that does not deduplicate events */
85
- req(relays, filters, id) {
85
+ req(relays, filters, opts) {
86
86
  // Never filter out offline relays in manual methods
87
- return this.group(relays, false).req(filters, id);
87
+ return this.group(relays, false).req(filters, opts);
88
88
  }
89
89
  /** Send an EVENT message to multiple relays */
90
90
  event(relays, event) {
package/dist/relay.d.ts CHANGED
@@ -4,21 +4,25 @@ import { Filter } from "applesauce-core/helpers/filter";
4
4
  import { BehaviorSubject, MonoTypeOperatorFunction, Observable, RepeatConfig, RetryConfig, Subject } from "rxjs";
5
5
  import { WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
6
6
  import { type NegentropySyncOptions, type ReconcileFunction } from "./negentropy.js";
7
- import { AuthSigner, CountResponse, FilterInput, IRelay, NegentropyReadStore, NegentropySyncStore, PublishOptions, PublishResponse, RelayInformation, RelayStatus, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
7
+ import { AuthSigner, FilterInput, NegentropyReadStore, NegentropySyncStore, PublishOptions, PublishResponse, RelayCountResponse, RelayInformation, RelayReqMessage, RelayReqOptions, RelayRequestCompleteOperator, RelayRequestOptions, RelayRequestResponse, RelayStatus, RelaySubscriptionOptions, RelaySubscriptionResponse } from "./types.js";
8
8
  /** Flags for the negentropy sync type */
9
9
  export declare enum SyncDirection {
10
10
  RECEIVE = 1,
11
11
  SEND = 2,
12
12
  BOTH = 3
13
13
  }
14
- /** An error that is thrown when a REQ is closed from the relay side */
15
- export declare class ReqCloseError extends Error {
14
+ /** Base error thrown when a relay closes a REQ or COUNT with a CLOSED message */
15
+ export declare class RelayClosedError extends Error {
16
+ readonly reason: string;
17
+ constructor(reason: string);
18
+ }
19
+ /** Thrown when the relay closes a subscription with an auth-required: prefix */
20
+ export declare class AuthRequiredError extends RelayClosedError {
21
+ constructor(reason: string);
16
22
  }
17
23
  export type RelayOptions = {
18
24
  /** Custom WebSocket implementation */
19
25
  WebSocket?: WebSocketSubjectConfig<any>["WebSocketCtor"];
20
- /** How long to wait for an EOSE message (default 10s) */
21
- eoseTimeout?: number;
22
26
  /** How long to wait for an OK message from the relay (default 10s) */
23
27
  eventTimeout?: number;
24
28
  /** How long to wait for a publish to complete (default 30s) */
@@ -38,14 +42,14 @@ export type RelayOptions = {
38
42
  now: number;
39
43
  attempts: number;
40
44
  }) => "reconnect" | "close" | "ignore";
41
- /** Default retry config for subscription() method */
42
- subscriptionRetry?: RetryConfig;
43
- /** Default retry config for request() method */
44
- requestRetry?: RetryConfig;
45
+ /** Default reconnect config for subscription() method */
46
+ subscriptionReconnect?: RetryConfig;
47
+ /** Default reconnect config for request() method */
48
+ requestReconnect?: RetryConfig;
45
49
  /** Default retry config for publish() method */
46
50
  publishRetry?: RetryConfig;
47
51
  };
48
- export declare class Relay implements IRelay {
52
+ export declare class Relay {
49
53
  url: string;
50
54
  protected log: typeof logger;
51
55
  protected socket: WebSocketSubject<any>;
@@ -103,7 +107,7 @@ export declare class Relay implements IRelay {
103
107
  open$: Subject<Event>;
104
108
  /** An observable that emits when underlying websocket is closed */
105
109
  close$: Subject<CloseEvent>;
106
- /** An observable that emits when underlying websocket is closing due to unsubscription */
110
+ /** An observable that emits when underlying websocket is closing due to unsubscribe or complete */
107
111
  closing$: Subject<void>;
108
112
  /** Tracks active req() operations by subscription ID */
109
113
  reqs$: BehaviorSubject<Record<string, Filter[]>>;
@@ -118,8 +122,6 @@ export declare class Relay implements IRelay {
118
122
  get information(): RelayInformation | null;
119
123
  get lastMessageAt(): number;
120
124
  get reqs(): Record<string, Filter[]>;
121
- /** If an EOSE message is not seen in this time, emit one locally (default 10s) */
122
- eoseTimeout: number;
123
125
  /** How long to wait for an OK message from the relay (default 10s) */
124
126
  eventTimeout: number;
125
127
  /** How long to wait for a publish to complete (default 30s) */
@@ -132,12 +134,12 @@ export declare class Relay implements IRelay {
132
134
  pingFrequency: number;
133
135
  /** How long to wait for EOSE response in milliseconds (default 20000) */
134
136
  pingTimeout: number;
135
- /** Default retry config for subscription() method */
136
- protected subscriptionReconnect: RetryConfig;
137
- /** Default retry config for request() method */
138
- protected requestReconnect: RetryConfig;
137
+ /** Default reconnect config for subscription() method */
138
+ subscriptionReconnect: RetryConfig;
139
+ /** Default reconnect config for request() method */
140
+ requestReconnect: RetryConfig;
139
141
  /** Default retry config for publish() method */
140
- protected publishRetry: RetryConfig;
142
+ publishRetry: RetryConfig;
141
143
  /** Policy hook for unresponsive connections */
142
144
  protected onUnresponsive?: RelayOptions["onUnresponsive"];
143
145
  protected receivedAuthRequiredForReq: BehaviorSubject<boolean>;
@@ -158,9 +160,9 @@ export declare class Relay implements IRelay {
158
160
  /** Send a message to the relay */
159
161
  send(message: any): void;
160
162
  /** Create a REQ observable that emits events or "EOSE" or errors */
161
- req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
163
+ req(filters: FilterInput, opts?: RelayReqOptions): Observable<RelayReqMessage>;
162
164
  /** Create a COUNT observable that emits a single count response */
163
- count(filters: Filter | Filter[], id?: string): Observable<CountResponse>;
165
+ count(filters: Filter | Filter[], id?: string): Observable<RelayCountResponse>;
164
166
  /** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
165
167
  event(event: NostrEvent, verb?: "EVENT" | "AUTH"): Observable<PublishResponse>;
166
168
  /** send and AUTH message */
@@ -169,22 +171,20 @@ export declare class Relay implements IRelay {
169
171
  negentropy(store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
170
172
  /** Authenticate with the relay using a signer */
171
173
  authenticate(signer: AuthSigner): Promise<PublishResponse>;
172
- /** Internal operator for creating the retry() operator */
174
+ /** Internal operator for creating the retry() operator for reconnecting to the websocket */
173
175
  protected customRetryOperator<T extends unknown = unknown>(times: undefined | boolean | number | RetryConfig, base?: RetryConfig): MonoTypeOperatorFunction<T>;
174
- /** Internal operator for creating the repeat() operator */
176
+ /** Internal operator for creating the repeat() operator for resubscribing */
175
177
  protected customRepeatOperator<T extends unknown = unknown>(times: undefined | boolean | number | RepeatConfig | undefined): MonoTypeOperatorFunction<T>;
176
178
  /** Internal operator for creating the timeout() operator */
177
179
  protected customTimeoutOperator<T extends unknown = unknown>(timeout: undefined | boolean | number, defaultTimeout: number): MonoTypeOperatorFunction<T>;
178
- /** Internal operator for handling auth-required errors from REQ/COUNT operations */
179
- protected handleAuthRequiredForReq(operation: "REQ" | "COUNT"): MonoTypeOperatorFunction<any>;
180
180
  /** Creates a REQ that retries when relay errors ( default 3 retries ) */
181
- subscription(filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
181
+ subscription(filters: FilterInput, opts?: RelaySubscriptionOptions): Observable<RelaySubscriptionResponse>;
182
182
  /** Makes a single request that retires on errors and completes on EOSE */
183
- request(filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
183
+ request(filters: FilterInput, opts?: RelayRequestOptions): Observable<RelayRequestResponse>;
184
184
  /** Publishes an event to the relay and retries when relay errors or responds with auth-required ( default 3 retries ) */
185
185
  publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse>;
186
186
  /** Negentropy sync events with the relay and an event store */
187
- sync(store: NegentropySyncStore, filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
187
+ sync(store: NegentropySyncStore, filters: Filter, direction?: SyncDirection): Observable<NostrEvent>;
188
188
  /** Force close the connection */
189
189
  close(): void;
190
190
  /** An async method that returns the NIP-11 information document for the relay */
@@ -197,4 +197,12 @@ export declare class Relay implements IRelay {
197
197
  static fetchInformationDocument(url: string): Observable<RelayInformation | null>;
198
198
  /** Static method to create a reconnection method for each relay */
199
199
  static createReconnectTimer(_relay: string): (_error?: Error | CloseEvent, tries?: number) => Observable<0>;
200
+ /** A complete condition that waits for the subscription to open */
201
+ static afterOpen(condition: RelayRequestCompleteOperator): RelayRequestCompleteOperator;
202
+ /** An OR complete condition, that completes when either condition is truthy */
203
+ static completeOr(...conditions: RelayRequestCompleteOperator[]): RelayRequestCompleteOperator;
204
+ /** An AND complete condition, that completes when all conditions are truthy */
205
+ static completeAnd(...conditions: RelayRequestCompleteOperator[]): RelayRequestCompleteOperator;
206
+ /** A default complete condition that waits for the subscription to open and then completes after a timeout */
207
+ static defaultComplete(timeout: number, afterOpen?: number): RelayRequestCompleteOperator;
200
208
  }