@peerbit/pubsub 5.3.9 → 5.3.11

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.
@@ -1,4 +1,5 @@
1
1
  import type { RustTopicRootDirectoryState } from "@peerbit/stream";
2
+ import { AbortError } from "@peerbit/time";
2
3
 
3
4
  const topicHash32 = (topic: string) => {
4
5
  let hash = 0x811c9dc5; // FNV-1a
@@ -9,12 +10,20 @@ const topicHash32 = (topic: string) => {
9
10
  return hash >>> 0;
10
11
  };
11
12
 
13
+ export type TopicRootResolutionOptions = {
14
+ signal?: AbortSignal;
15
+ };
16
+
12
17
  export type TopicRootResolver = (
13
18
  topic: string,
19
+ options?: TopicRootResolutionOptions,
14
20
  ) => string | undefined | Promise<string | undefined>;
15
21
 
16
22
  export type TopicRootTracker = {
17
- resolveRoot(topic: string): string | undefined | Promise<string | undefined>;
23
+ resolveRoot(
24
+ topic: string,
25
+ options?: TopicRootResolutionOptions,
26
+ ): string | undefined | Promise<string | undefined>;
18
27
  };
19
28
 
20
29
  export type TopicRootDirectoryOptions = {
@@ -22,6 +31,43 @@ export type TopicRootDirectoryOptions = {
22
31
  resolver?: TopicRootResolver;
23
32
  };
24
33
 
34
+ const abortReason = (signal: AbortSignal) =>
35
+ signal.reason ?? new AbortError("Topic-root resolution aborted");
36
+
37
+ const throwIfAborted = (signal?: AbortSignal): void => {
38
+ if (signal?.aborted) throw abortReason(signal);
39
+ };
40
+
41
+ const awaitWithSignal = async <T>(
42
+ value: T | PromiseLike<T>,
43
+ signal?: AbortSignal,
44
+ ): Promise<T> => {
45
+ if (signal?.aborted) {
46
+ void Promise.resolve(value).catch(() => {});
47
+ throw abortReason(signal);
48
+ }
49
+ if (!signal) return await value;
50
+
51
+ return new Promise<T>((resolve, reject) => {
52
+ const onAbort = () => {
53
+ cleanup();
54
+ reject(abortReason(signal));
55
+ };
56
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
57
+ signal.addEventListener("abort", onAbort, { once: true });
58
+ Promise.resolve(value).then(
59
+ (result) => {
60
+ cleanup();
61
+ resolve(result);
62
+ },
63
+ (error) => {
64
+ cleanup();
65
+ reject(error);
66
+ },
67
+ );
68
+ });
69
+ };
70
+
25
71
  export class TopicRootDirectory {
26
72
  private readonly explicitRootsByTopic = new Map<string, string>();
27
73
  private defaultCandidates: string[] = [];
@@ -103,18 +149,27 @@ export class TopicRootDirectory {
103
149
  this.resolver = resolver;
104
150
  }
105
151
 
106
- public async resolveRoot(topic: string): Promise<string | undefined> {
107
- const local = await this.resolveLocal(topic);
152
+ public async resolveRoot(
153
+ topic: string,
154
+ options?: TopicRootResolutionOptions,
155
+ ): Promise<string | undefined> {
156
+ const local = await this.resolveLocal(topic, options);
108
157
  if (local) return local;
109
158
 
159
+ throwIfAborted(options?.signal);
110
160
  return this.resolveDeterministicCandidate(topic);
111
161
  }
112
162
 
113
- public async resolveLocal(topic: string): Promise<string | undefined> {
163
+ public async resolveLocal(
164
+ topic: string,
165
+ options?: TopicRootResolutionOptions,
166
+ ): Promise<string | undefined> {
167
+ throwIfAborted(options?.signal);
114
168
  const explicit = this.getRoot(topic);
115
169
  if (explicit) return explicit;
116
170
 
117
- return this.resolver?.(topic);
171
+ if (!this.resolver) return undefined;
172
+ return awaitWithSignal(this.resolver(topic, options), options?.signal);
118
173
  }
119
174
 
120
175
  public resolveDeterministicCandidate(topic: string): string | undefined {
@@ -183,45 +238,64 @@ export class TopicRootControlPlane {
183
238
  return [...this.trackers];
184
239
  }
185
240
 
186
- public resolveLocalTopicRoot(topic: string) {
187
- return this.directory.resolveLocal(topic);
241
+ public resolveLocalTopicRoot(
242
+ topic: string,
243
+ options?: TopicRootResolutionOptions,
244
+ ) {
245
+ return this.directory.resolveLocal(topic, options);
188
246
  }
189
247
 
190
248
  public resolveDeterministicTopicRoot(topic: string) {
191
249
  return this.directory.resolveDeterministicCandidate(topic);
192
250
  }
193
251
 
194
- public resolveCanonicalTopicRoot(topic: string) {
195
- return this.directory.resolveRoot(topic);
252
+ public resolveCanonicalTopicRoot(
253
+ topic: string,
254
+ options?: TopicRootResolutionOptions,
255
+ ) {
256
+ return this.directory.resolveRoot(topic, options);
196
257
  }
197
258
 
198
- public resolveTrackedTopicRoot(topic: string) {
199
- return this.resolveWithTrackers(topic, false);
259
+ public resolveTrackedTopicRoot(
260
+ topic: string,
261
+ options?: TopicRootResolutionOptions,
262
+ ) {
263
+ return this.resolveWithTrackers(topic, false, options);
200
264
  }
201
265
 
202
- public resolveTopicRoot(topic: string) {
203
- return this.resolveWithTrackers(topic, true);
266
+ public resolveTopicRoot(
267
+ topic: string,
268
+ options?: TopicRootResolutionOptions,
269
+ ) {
270
+ return this.resolveWithTrackers(topic, true, options);
204
271
  }
205
272
 
206
273
  private async resolveWithTrackers(
207
274
  topic: string,
208
275
  fallbackToDeterministic = true,
276
+ options?: TopicRootResolutionOptions,
209
277
  ): Promise<string | undefined> {
210
- const local = await this.directory.resolveLocal(topic);
278
+ const local = await this.directory.resolveLocal(topic, options);
211
279
  if (local) {
212
280
  return local;
213
281
  }
214
282
 
215
283
  for (const tracker of this.trackers) {
216
284
  try {
217
- const resolved = await tracker.resolveRoot(topic);
285
+ throwIfAborted(options?.signal);
286
+ const resolved = await awaitWithSignal(
287
+ tracker.resolveRoot(topic, options),
288
+ options?.signal,
289
+ );
218
290
  if (resolved) {
219
291
  return resolved;
220
292
  }
221
293
  } catch {
294
+ throwIfAborted(options?.signal);
222
295
  // ignore tracker failures and continue with remaining trackers
223
296
  }
224
297
  }
298
+ throwIfAborted(options?.signal);
225
299
  return fallbackToDeterministic
226
300
  ? this.directory.resolveDeterministicCandidate(topic)
227
301
  : undefined;