applesauce-loaders 0.10.0 → 0.12.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.
Files changed (67) hide show
  1. package/dist/helpers/__tests__/address-pointer.test.d.ts +1 -0
  2. package/dist/helpers/__tests__/address-pointer.test.js +19 -0
  3. package/dist/helpers/address-pointer.d.ts +15 -2
  4. package/dist/helpers/address-pointer.js +48 -4
  5. package/dist/helpers/array.d.ts +0 -2
  6. package/dist/helpers/array.js +0 -17
  7. package/dist/helpers/dns-identity.d.ts +40 -0
  8. package/dist/helpers/dns-identity.js +50 -0
  9. package/dist/helpers/event-pointer.d.ts +5 -0
  10. package/dist/helpers/event-pointer.js +19 -0
  11. package/dist/helpers/index.d.ts +1 -0
  12. package/dist/helpers/index.js +1 -0
  13. package/dist/helpers/pointer.d.ts +11 -0
  14. package/dist/helpers/pointer.js +47 -0
  15. package/dist/helpers/rx-nostr.d.ts +2 -0
  16. package/dist/helpers/rx-nostr.js +5 -0
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/loaders/__tests__/dns-identity-loader.test.d.ts +1 -0
  20. package/dist/loaders/__tests__/dns-identity-loader.test.js +59 -0
  21. package/dist/loaders/__tests__/relay-timeline-loader.test.d.ts +1 -0
  22. package/dist/loaders/__tests__/relay-timeline-loader.test.js +26 -0
  23. package/dist/loaders/__tests__/request-loader.test.d.ts +1 -0
  24. package/dist/loaders/__tests__/request-loader.test.js +37 -0
  25. package/dist/loaders/cache-timeline-loader.d.ts +22 -0
  26. package/dist/loaders/cache-timeline-loader.js +61 -0
  27. package/dist/loaders/dns-identity-loader.d.ts +25 -0
  28. package/dist/loaders/dns-identity-loader.js +66 -0
  29. package/dist/loaders/index.d.ts +8 -1
  30. package/dist/loaders/index.js +8 -1
  31. package/dist/loaders/loader.d.ts +14 -8
  32. package/dist/loaders/loader.js +7 -2
  33. package/dist/loaders/relay-timeline-loader.d.ts +24 -0
  34. package/dist/loaders/relay-timeline-loader.js +70 -0
  35. package/dist/loaders/replaceable-loader.d.ts +7 -15
  36. package/dist/loaders/replaceable-loader.js +49 -106
  37. package/dist/loaders/request-loader.d.ts +30 -0
  38. package/dist/loaders/request-loader.js +57 -0
  39. package/dist/loaders/single-event-loader.d.ts +26 -0
  40. package/dist/loaders/single-event-loader.js +76 -0
  41. package/dist/loaders/tag-value-loader.d.ts +33 -0
  42. package/dist/loaders/tag-value-loader.js +75 -0
  43. package/dist/loaders/timeline-loader.d.ts +22 -0
  44. package/dist/loaders/timeline-loader.js +56 -0
  45. package/dist/loaders/user-sets-loader.d.ts +31 -0
  46. package/dist/loaders/user-sets-loader.js +66 -0
  47. package/dist/operators/__tests__/distinct-relays.test.d.ts +1 -0
  48. package/dist/operators/__tests__/distinct-relays.test.js +75 -0
  49. package/dist/operators/__tests__/generator-sequence.test.d.ts +1 -0
  50. package/dist/operators/__tests__/generator-sequence.test.js +38 -0
  51. package/dist/operators/distinct-relays.d.ts +4 -0
  52. package/dist/operators/distinct-relays.js +14 -0
  53. package/dist/operators/distinct-timeout.d.ts +3 -0
  54. package/dist/operators/distinct-timeout.js +15 -0
  55. package/dist/operators/generator-sequence.d.ts +1 -1
  56. package/dist/operators/generator-sequence.js +42 -34
  57. package/dist/operators/index.d.ts +2 -3
  58. package/dist/operators/index.js +2 -3
  59. package/package.json +28 -8
  60. package/dist/loaders/single-relay-replaceable-loader.d.ts +0 -14
  61. package/dist/loaders/single-relay-replaceable-loader.js +0 -51
  62. package/dist/operators/address-pointers-request.d.ts +0 -5
  63. package/dist/operators/address-pointers-request.js +0 -25
  64. package/dist/operators/max-filters.d.ts +0 -4
  65. package/dist/operators/max-filters.js +0 -8
  66. package/dist/operators/relay-request.d.ts +0 -4
  67. package/dist/operators/relay-request.js +0 -9
@@ -0,0 +1,66 @@
1
+ import { unixNow } from "applesauce-core/helpers";
2
+ import { getIdentitiesFromJson, IdentityStatus, normalizeIdentityJson, } from "../helpers/dns-identity.js";
3
+ export class DnsIdentityLoader {
4
+ cache;
5
+ identities = new Map();
6
+ /** The fetch implementation this class should use */
7
+ fetch = fetch;
8
+ /** How long an identity should be kept until its considered expired (in seconds) defaults to 1 week */
9
+ expiration = 60 * 60 * 24 * 7;
10
+ constructor(cache) {
11
+ this.cache = cache;
12
+ }
13
+ /** Makes an http request to fetch an identity */
14
+ async fetchIdentity(name, domain) {
15
+ const { fetch } = this;
16
+ const checked = unixNow();
17
+ try {
18
+ const res = await fetch(`https://${domain}/.well-known/nostr.json?name=${name}`, { redirect: "manual" });
19
+ if (res.status !== 200)
20
+ throw Error("Wrong response code");
21
+ const json = await res.json().then(normalizeIdentityJson);
22
+ const identities = getIdentitiesFromJson(domain, json, checked);
23
+ // save all identities to cache
24
+ if (this.cache && Object.values(identities).length > 0)
25
+ this.cache.save(identities);
26
+ for (const [address, identity] of Object.entries(identities))
27
+ this.identities.set(address, identity);
28
+ return identities[name + "@" + domain] || { name, domain, checked, status: IdentityStatus.Missing };
29
+ }
30
+ catch (error) {
31
+ if (error instanceof Error)
32
+ return { name, domain, checked, status: IdentityStatus.Error, error: error.message };
33
+ else
34
+ return { name, domain, checked, status: IdentityStatus.Error, error: "Unknown" };
35
+ }
36
+ }
37
+ /** Loads an identity from the cache or fetches it */
38
+ async loadIdentity(name, domain) {
39
+ let identity;
40
+ if (this.cache)
41
+ identity = await this.cache.load(name + "@" + domain);
42
+ // fetch the identity if its not in cache, or if its expired
43
+ if (!identity || unixNow() - identity.checked > this.expiration)
44
+ return await this.fetchIdentity(name, domain);
45
+ else
46
+ return identity;
47
+ }
48
+ requesting = new Map();
49
+ /** Requests an identity to be loaded */
50
+ requestIdentity(name, domain) {
51
+ const key = name + "@" + domain;
52
+ let existing = this.identities.get(key);
53
+ if (existing)
54
+ return Promise.resolve(existing);
55
+ let ongoing = this.requesting.get(key);
56
+ if (!ongoing) {
57
+ ongoing = this.loadIdentity(name, domain);
58
+ this.requesting.set(key, ongoing);
59
+ }
60
+ return ongoing;
61
+ }
62
+ /** Checks if an identity is loaded */
63
+ getIdentity(name, domain) {
64
+ return this.identities.get(name + "@" + domain);
65
+ }
66
+ }
@@ -1,3 +1,10 @@
1
1
  export * from "./loader.js";
2
2
  export * from "./replaceable-loader.js";
3
- export * from "./single-relay-replaceable-loader.js";
3
+ export * from "./single-event-loader.js";
4
+ export * from "./user-sets-loader.js";
5
+ export * from "./timeline-loader.js";
6
+ export * from "./relay-timeline-loader.js";
7
+ export * from "./cache-timeline-loader.js";
8
+ export * from "./tag-value-loader.js";
9
+ export * from "./request-loader.js";
10
+ export * from "./dns-identity-loader.js";
@@ -1,3 +1,10 @@
1
1
  export * from "./loader.js";
2
2
  export * from "./replaceable-loader.js";
3
- export * from "./single-relay-replaceable-loader.js";
3
+ export * from "./single-event-loader.js";
4
+ export * from "./user-sets-loader.js";
5
+ export * from "./timeline-loader.js";
6
+ export * from "./relay-timeline-loader.js";
7
+ export * from "./cache-timeline-loader.js";
8
+ export * from "./tag-value-loader.js";
9
+ export * from "./request-loader.js";
10
+ export * from "./dns-identity-loader.js";
@@ -1,14 +1,20 @@
1
- import { Observable, OperatorFunction, Subject, Subscribable } from "rxjs";
1
+ import { Filter, NostrEvent } from "nostr-tools";
2
+ import { InteropObservable, Observable, OperatorFunction, Subject, Subscribable } from "rxjs";
3
+ export type RelayFilterMap<T = Filter> = {
4
+ [relay: string]: T[];
5
+ };
6
+ export type CacheRequest = (filters: Filter[]) => Observable<NostrEvent>;
2
7
  export interface ILoader<T, R> extends Subscribable<R> {
3
8
  next: (value: T) => void;
4
9
  pipe: Observable<R>["pipe"];
5
10
  }
6
11
  /** Base loader class */
7
- export declare class Loader<T, R> implements ILoader<T, R> {
8
- protected subject: Subject<T>;
9
- protected observable: Observable<R>;
10
- pipe: Observable<R>["pipe"];
11
- subscribe: Observable<R>["subscribe"];
12
- constructor(transform: OperatorFunction<T, R>);
13
- next(value: T): void;
12
+ export declare class Loader<Input, Output> implements ILoader<Input, Output>, InteropObservable<Output> {
13
+ protected subject: Subject<Input>;
14
+ observable: Observable<Output>;
15
+ pipe: Observable<Output>["pipe"];
16
+ subscribe: Observable<Output>["subscribe"];
17
+ constructor(transform: OperatorFunction<Input, Output>);
18
+ next(value: Input): void;
19
+ [Symbol.observable](): Observable<Output>;
14
20
  }
@@ -1,4 +1,4 @@
1
- import { Subject } from "rxjs";
1
+ import { share, Subject } from "rxjs";
2
2
  /** Base loader class */
3
3
  export class Loader {
4
4
  subject = new Subject();
@@ -6,7 +6,9 @@ export class Loader {
6
6
  pipe;
7
7
  subscribe;
8
8
  constructor(transform) {
9
- this.observable = this.subject.pipe(transform);
9
+ this.observable = this.subject.pipe(transform,
10
+ // only create a single instance of the transformer
11
+ share());
10
12
  // copy pipe function
11
13
  this.pipe = this.observable.pipe.bind(this.observable);
12
14
  this.subscribe = this.observable.subscribe.bind(this.observable);
@@ -14,4 +16,7 @@ export class Loader {
14
16
  next(value) {
15
17
  this.subject.next(value);
16
18
  }
19
+ [Symbol.observable]() {
20
+ return this.observable;
21
+ }
17
22
  }
@@ -0,0 +1,24 @@
1
+ import { EventPacket, RxNostr } from "rx-nostr";
2
+ import { BehaviorSubject } from "rxjs";
3
+ import { logger } from "applesauce-core";
4
+ import { Filter } from "nostr-tools";
5
+ import { Loader } from "./loader.js";
6
+ export type TimelessFilter = Omit<Filter, "since" | "until">;
7
+ export type RelayTimelineLoaderOptions = {
8
+ /** default number of events to request in each batch */
9
+ limit?: number;
10
+ };
11
+ /** A loader that can be used to load a timeline in chunks */
12
+ export declare class RelayTimelineLoader extends Loader<number | void, EventPacket> {
13
+ relay: string;
14
+ filters: TimelessFilter[];
15
+ id: string;
16
+ loading$: BehaviorSubject<boolean>;
17
+ get loading(): boolean;
18
+ /** current "until" timestamp */
19
+ cursor: number;
20
+ /** if the timeline is complete */
21
+ complete: boolean;
22
+ protected log: typeof logger;
23
+ constructor(rxNostr: RxNostr, relay: string, filters: TimelessFilter[], opts?: RelayTimelineLoaderOptions);
24
+ }
@@ -0,0 +1,70 @@
1
+ import { createRxOneshotReq } from "rx-nostr";
2
+ import { BehaviorSubject, filter, map, Observable } from "rxjs";
3
+ import { logger } from "applesauce-core";
4
+ import { nanoid } from "nanoid";
5
+ import { unixNow } from "applesauce-core/helpers";
6
+ import { Loader } from "./loader.js";
7
+ /** A loader that can be used to load a timeline in chunks */
8
+ export class RelayTimelineLoader extends Loader {
9
+ relay;
10
+ filters;
11
+ id = nanoid(8);
12
+ loading$ = new BehaviorSubject(false);
13
+ get loading() {
14
+ return this.loading$.value;
15
+ }
16
+ /** current "until" timestamp */
17
+ cursor = Infinity;
18
+ /** if the timeline is complete */
19
+ complete = false;
20
+ log = logger.extend("RelayTimelineLoader");
21
+ constructor(rxNostr, relay, filters, opts) {
22
+ super((source) => new Observable((observer) => {
23
+ return source
24
+ .pipe(filter(() => !this.loading && !this.complete), map((limit) => {
25
+ // build next batch filters
26
+ return filters.map((filter) => ({
27
+ limit: limit || opts?.limit,
28
+ ...filter,
29
+ // limit curser to now
30
+ until: Math.min(unixNow(), this.cursor),
31
+ }));
32
+ }),
33
+ // ignore empty filters
34
+ filter((filters) => filters.length > 0))
35
+ .subscribe((filters) => {
36
+ // make batch request
37
+ let count = 0;
38
+ const req = createRxOneshotReq({ filters, rxReqId: this.id });
39
+ this.loading$.next(true);
40
+ this.log(`Next batch starting at ${filters[0].until} limit ${filters[0].limit}`);
41
+ rxNostr.use(req, { on: { relays: [relay] } }).subscribe({
42
+ next: (packet) => {
43
+ // update cursor when event is received
44
+ this.cursor = Math.min(packet.event.created_at - 1, this.cursor);
45
+ count++;
46
+ // forward packet
47
+ observer.next(packet);
48
+ },
49
+ error: (err) => observer.error(err),
50
+ complete: () => {
51
+ // set loading to false when batch completes
52
+ this.loading$.next(false);
53
+ // set complete the observable if 0 events where returned
54
+ if (count === 0) {
55
+ observer.complete();
56
+ this.log(`Got ${count} event, Complete`);
57
+ }
58
+ else {
59
+ this.log(`Finished batch, got ${count} events`);
60
+ }
61
+ },
62
+ });
63
+ });
64
+ }));
65
+ this.relay = relay;
66
+ this.filters = filters;
67
+ // create a unique logger for this instance
68
+ this.log = this.log.extend(relay);
69
+ }
70
+ }
@@ -1,31 +1,23 @@
1
- import { Observable } from "rxjs";
2
1
  import { EventPacket, RxNostr } from "rx-nostr";
3
- import { Filter, NostrEvent } from "nostr-tools";
4
2
  import { logger } from "applesauce-core";
5
- import { Loader } from "./loader.js";
6
- export type LoadableAddressPointer = {
7
- kind: number;
8
- pubkey: string;
9
- /** Optional "d" tag for paramaritized replaceable */
10
- identifier?: string;
11
- /** Relays to load from */
12
- relays?: string[];
13
- /** Load this address pointer even if it has already been loaded */
14
- force?: boolean;
15
- };
16
- export type CacheRequest = (filters: Filter[]) => Observable<NostrEvent>;
3
+ import { CacheRequest, Loader } from "./loader.js";
4
+ import { LoadableAddressPointer } from "../helpers/address-pointer.js";
17
5
  export type ReplaceableLoaderOptions = {
18
6
  /**
19
7
  * Time interval to buffer requests in ms
20
8
  * @default 1000
21
9
  */
22
10
  bufferTime?: number;
23
- /** Request events from the cache first */
11
+ /** A method used to load events from a local cache */
24
12
  cacheRequest?: CacheRequest;
25
13
  /** Fallback lookup relays to check when event cant be found */
26
14
  lookupRelays?: string[];
27
15
  };
28
16
  export declare class ReplaceableLoader extends Loader<LoadableAddressPointer, EventPacket> {
29
17
  log: typeof logger;
18
+ /** A method used to load events from a local cache */
19
+ cacheRequest?: CacheRequest;
20
+ /** Fallback lookup relays to check when event cant be found */
21
+ lookupRelays?: string[];
30
22
  constructor(rxNostr: RxNostr, opts?: ReplaceableLoaderOptions);
31
23
  }
@@ -1,23 +1,16 @@
1
- import { share, tap, from, filter, bufferTime, map, mergeMap } from "rxjs";
2
- import { getEventUID, getReplaceableUID, markFromCache } from "applesauce-core/helpers";
1
+ import { tap, filter, bufferTime, map } from "rxjs";
2
+ import { createRxOneshotReq } from "rx-nostr";
3
+ import { getEventUID, markFromCache, mergeRelaySets } from "applesauce-core/helpers";
3
4
  import { logger } from "applesauce-core";
4
5
  import { nanoid } from "nanoid";
5
6
  import { Loader } from "./loader.js";
6
7
  import { generatorSequence } from "../operators/generator-sequence.js";
7
- import { replaceableRequest } from "../operators/address-pointers-request.js";
8
- import { createFiltersFromAddressPointers, getAddressPointerId, getRelaysFromPointers, isLoadableAddressPointer, } from "../helpers/address-pointer.js";
9
- import { unique } from "../helpers/array.js";
10
- /** deep clone a loadable pointer to ensure its safe to modify */
11
- function cloneLoadablePointer(pointer) {
12
- const clone = { ...pointer };
13
- if (pointer.relays)
14
- clone.relays = [...pointer.relays];
15
- return clone;
16
- }
8
+ import { consolidateAddressPointers, createFiltersFromAddressPointers, getAddressPointerId, getRelaysFromPointers, isLoadableAddressPointer, } from "../helpers/address-pointer.js";
9
+ import { getDefaultReadRelays } from "../helpers/rx-nostr.js";
10
+ import { distinctRelaysBatch } from "../operators/distinct-relays.js";
17
11
  /** A generator that tries to load the address pointers from the cache first, then tries the relays */
18
12
  function* cacheFirstSequence(rxNostr, pointers, log, opts) {
19
- const id = nanoid(8);
20
- log = log.extend(id);
13
+ const id = nanoid(4);
21
14
  let remaining = Array.from(pointers);
22
15
  const pointerRelays = Array.from(getRelaysFromPointers(pointers));
23
16
  // handle previous step results and decide if to exit
@@ -26,40 +19,40 @@ function* cacheFirstSequence(rxNostr, pointers, log, opts) {
26
19
  const coordinates = new Set(results.map((p) => getEventUID(p.event)));
27
20
  // if there where results, filter out any pointers that where found
28
21
  remaining = remaining.filter((pointer) => {
29
- const found = coordinates.has(getReplaceableUID(pointer.kind, pointer.pubkey, pointer.identifier));
22
+ const found = coordinates.has(getAddressPointerId(pointer));
30
23
  if (found && pointer.force !== true)
31
24
  return false;
32
25
  else
33
26
  return true;
34
27
  });
35
- if (remaining.length === 0)
28
+ // If there are none left, complete
29
+ if (remaining.length === 0) {
30
+ log(`[${id}] Complete`);
36
31
  return true;
32
+ }
37
33
  }
38
34
  return false;
39
35
  };
40
36
  // first attempt, load from cache relays
41
37
  if (opts?.cacheRequest) {
42
- log(`Checking cache`, remaining);
43
- const results = yield from([remaining]).pipe(
44
- // convert pointers to filters
45
- map(createFiltersFromAddressPointers),
46
- // make requests
47
- mergeMap((filters) => opts.cacheRequest(filters)),
38
+ log(`[${id}] Checking cache`, remaining);
39
+ const filters = createFiltersFromAddressPointers(remaining);
40
+ const results = yield opts.cacheRequest(filters).pipe(
48
41
  // mark the event as from the cache
49
42
  tap((event) => markFromCache(event)),
50
43
  // convert to event packets
51
- map((e) => ({ event: e, from: "cache", subId: "cache", type: "EVENT" })));
44
+ map((e) => ({ event: e, from: "", subId: "replaceable-loader", type: "EVENT" })));
52
45
  if (handleResults(results))
53
46
  return;
54
47
  }
55
48
  // load from pointer relays and default relays
56
- const defaultRelays = Object.entries(rxNostr.getDefaultRelays())
57
- .filter(([_relay, config]) => config.read)
58
- .map(([relay]) => relay);
59
- const remoteRelays = [...pointerRelays, ...defaultRelays];
49
+ const defaultRelays = getDefaultReadRelays(rxNostr);
50
+ const remoteRelays = mergeRelaySets(pointerRelays, defaultRelays);
60
51
  if (remoteRelays.length > 0) {
61
- log(`Requesting`, remoteRelays, remaining);
62
- const results = yield from([remaining]).pipe(replaceableRequest(rxNostr, id, remoteRelays));
52
+ log(`[${id}] Requesting`, remoteRelays, remaining);
53
+ const filters = createFiltersFromAddressPointers(remaining);
54
+ const req = createRxOneshotReq({ filters, rxReqId: id });
55
+ const results = yield rxNostr.use(req, { on: { relays: remoteRelays } });
63
56
  if (handleResults(results))
64
57
  return;
65
58
  }
@@ -68,96 +61,46 @@ function* cacheFirstSequence(rxNostr, pointers, log, opts) {
68
61
  // make sure we aren't asking a relay twice
69
62
  const relays = opts.lookupRelays.filter((r) => !pointerRelays.includes(r));
70
63
  if (relays.length > 0) {
71
- log(`Request from lookup`, relays, remaining);
72
- const results = yield from([remaining]).pipe(replaceableRequest(rxNostr, id, relays));
64
+ log(`[${id}] Request from lookup`, relays, remaining);
65
+ const filters = createFiltersFromAddressPointers(remaining);
66
+ const req = createRxOneshotReq({ filters, rxReqId: id });
67
+ const results = yield rxNostr.use(req, { on: { relays } });
73
68
  if (handleResults(results))
74
69
  return;
75
70
  }
76
71
  }
77
72
  }
78
- /** Batches address pointers and consolidates them */
79
- function multiRelayBatcher(buffer) {
80
- const requestedFrom = new Map();
81
- const requestedDefault = new Set();
82
- return (source) => source.pipe(
83
- // buffer on time
84
- bufferTime(buffer),
85
- // ignore empty buffers
86
- filter((buffer) => buffer.length > 0),
87
- // consolidate buffered pointers
88
- map((pointers) => {
89
- const byId = new Map();
90
- for (const pointer of pointers) {
91
- const id = getAddressPointerId(pointer);
92
- if (byId.has(id)) {
93
- // duplicate, merge pointers
94
- const current = byId.get(id);
95
- // merge relays
96
- if (pointer.relays) {
97
- if (current.relays)
98
- current.relays = unique([...current.relays, ...pointer.relays]);
99
- else
100
- current.relays = pointer.relays;
101
- }
102
- // merge force flag
103
- if (pointer.force !== undefined) {
104
- current.force = current.force || pointer.force;
105
- }
106
- }
107
- else
108
- byId.set(id, cloneLoadablePointer(pointer));
109
- }
110
- // return consolidated pointers
111
- return Array.from(byId.values());
112
- }),
113
- // ignore empty buffer
114
- filter((buffer) => buffer.length > 0),
115
- // ensure pointers are only requested from each relay once
116
- map((pointers) => {
117
- return pointers.filter((pointer) => {
118
- const id = getAddressPointerId(pointer);
119
- // skip if forced
120
- if (pointer.force)
121
- return true;
122
- // skip if already requested from default relays
123
- if (!pointer.relays) {
124
- if (requestedDefault.has(id))
125
- return false;
126
- else {
127
- requestedDefault.add(id);
128
- return true;
129
- }
130
- }
131
- let set = requestedFrom.get(id);
132
- if (!set) {
133
- set = new Set();
134
- requestedFrom.set(id, set);
135
- }
136
- // remove any relays that have already been used
137
- pointer.relays = pointer.relays.filter((relay) => !set.has(relay));
138
- // remember used relays
139
- for (const relay of pointer.relays)
140
- set.add(relay);
141
- // if there are no new relays, ignore pointer
142
- return pointer.relays.length > 0;
143
- });
144
- }),
145
- // ignore empty buffer
146
- filter((buffer) => buffer.length > 0));
147
- }
148
73
  export class ReplaceableLoader extends Loader {
149
74
  log = logger.extend("ReplaceableLoader");
75
+ /** A method used to load events from a local cache */
76
+ cacheRequest;
77
+ /** Fallback lookup relays to check when event cant be found */
78
+ lookupRelays;
150
79
  constructor(rxNostr, opts) {
151
80
  super((source) => {
152
81
  return source.pipe(
153
82
  // filter out invalid pointers
154
83
  filter(isLoadableAddressPointer),
155
- // batch and filter
156
- multiRelayBatcher(opts?.bufferTime ?? 1000),
84
+ // buffer on time
85
+ bufferTime(opts?.bufferTime ?? 1000),
86
+ // ignore empty buffers
87
+ filter((buffer) => buffer.length > 0),
88
+ // only fetch from each relay once
89
+ distinctRelaysBatch(getAddressPointerId),
90
+ // consolidate buffered pointers
91
+ map(consolidateAddressPointers),
92
+ // ignore empty buffer
93
+ filter((buffer) => buffer.length > 0),
157
94
  // check cache, relays, lookup relays in that order
158
- generatorSequence((pointers) => cacheFirstSequence(rxNostr, pointers, this.log, opts)),
159
- // share the response with all subscribers
160
- share());
95
+ generatorSequence((pointers) => cacheFirstSequence(rxNostr, pointers, this.log, {
96
+ cacheRequest: this.cacheRequest,
97
+ lookupRelays: this.lookupRelays,
98
+ }),
99
+ // there will always be more events, never complete
100
+ false));
161
101
  });
102
+ // set options
103
+ this.cacheRequest = opts?.cacheRequest;
104
+ this.lookupRelays = opts?.lookupRelays;
162
105
  }
163
106
  }
@@ -0,0 +1,30 @@
1
+ import { IEventStore, QueryStore } from "applesauce-core";
2
+ import { ProfilePointer } from "nostr-tools/nip19";
3
+ import { Observable } from "rxjs";
4
+ import { ReplaceableLoader } from "./replaceable-loader.js";
5
+ import { LoadableAddressPointer } from "../helpers/address-pointer.js";
6
+ /** A special Promised based loader built on the {@link QueryStore} */
7
+ export declare class RequestLoader {
8
+ store: QueryStore;
9
+ requestTimeout: number;
10
+ replaceableLoader?: ReplaceableLoader;
11
+ constructor(store: QueryStore);
12
+ protected runWithTimeout<T extends unknown, Args extends Array<any>>(queryConstructor: (...args: Args) => {
13
+ key: string;
14
+ run: (events: IEventStore, store: QueryStore) => Observable<T>;
15
+ }, ...args: Args): Promise<NonNullable<T>>;
16
+ protected checkReplaceable(): ReplaceableLoader;
17
+ /** Requests a single replaceable event */
18
+ replaceable(pointer: LoadableAddressPointer, force?: boolean): Promise<import("nostr-tools").Event>;
19
+ /** Loads a pubkeys profile */
20
+ profile(pointer: ProfilePointer, force?: boolean): Promise<import("applesauce-core/helpers").ProfileContent>;
21
+ /** Loads a pubkeys profile */
22
+ mailboxes(pointer: ProfilePointer, force?: boolean): Promise<{
23
+ inboxes: string[];
24
+ outboxes: string[];
25
+ }>;
26
+ /** Loads a pubkeys profile */
27
+ contacts(pointer: ProfilePointer, force?: boolean): Promise<ProfilePointer[]>;
28
+ /** Loads a pubkeys blossom servers */
29
+ blossomServers(pointer: ProfilePointer, force?: boolean): Promise<URL[]>;
30
+ }
@@ -0,0 +1,57 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { MailboxesQuery, ProfileQuery, ReplaceableQuery, UserBlossomServersQuery, UserContactsQuery, } from "applesauce-core/queries";
3
+ import { getObservableValue, simpleTimeout } from "applesauce-core/observable";
4
+ import { filter } from "rxjs";
5
+ import { BLOSSOM_SERVER_LIST_KIND } from "applesauce-core/helpers";
6
+ /** A special Promised based loader built on the {@link QueryStore} */
7
+ export class RequestLoader {
8
+ store;
9
+ requestTimeout = 10_000;
10
+ replaceableLoader;
11
+ constructor(store) {
12
+ this.store = store;
13
+ }
14
+ // hacky method to run queries with timeouts
15
+ async runWithTimeout(queryConstructor, ...args) {
16
+ return getObservableValue(this.store.createQuery(queryConstructor, ...args).pipe(
17
+ // ignore undefined and null values
18
+ filter((v) => v !== undefined && v !== null),
19
+ // timeout with an error is not values
20
+ simpleTimeout(this.requestTimeout)));
21
+ }
22
+ checkReplaceable() {
23
+ if (!this.replaceableLoader)
24
+ throw new Error("Missing ReplaceableLoader");
25
+ return this.replaceableLoader;
26
+ }
27
+ /** Requests a single replaceable event */
28
+ replaceable(pointer, force) {
29
+ this.checkReplaceable().next({ ...pointer, force });
30
+ return this.runWithTimeout(ReplaceableQuery, pointer.kind, pointer.pubkey, pointer.identifier);
31
+ }
32
+ /** Loads a pubkeys profile */
33
+ profile(pointer, force) {
34
+ this.checkReplaceable().next({ kind: kinds.Metadata, pubkey: pointer.pubkey, relays: pointer.relays, force });
35
+ return this.runWithTimeout(ProfileQuery, pointer.pubkey);
36
+ }
37
+ /** Loads a pubkeys profile */
38
+ mailboxes(pointer, force) {
39
+ this.checkReplaceable().next({ kind: kinds.RelayList, pubkey: pointer.pubkey, relays: pointer.relays, force });
40
+ return this.runWithTimeout(MailboxesQuery, pointer.pubkey);
41
+ }
42
+ /** Loads a pubkeys profile */
43
+ contacts(pointer, force) {
44
+ this.checkReplaceable().next({ kind: kinds.Contacts, pubkey: pointer.pubkey, relays: pointer.relays, force });
45
+ return this.runWithTimeout(UserContactsQuery, pointer.pubkey);
46
+ }
47
+ /** Loads a pubkeys blossom servers */
48
+ blossomServers(pointer, force) {
49
+ this.checkReplaceable().next({
50
+ kind: BLOSSOM_SERVER_LIST_KIND,
51
+ pubkey: pointer.pubkey,
52
+ relays: pointer.relays,
53
+ force,
54
+ });
55
+ return this.runWithTimeout(UserBlossomServersQuery, pointer.pubkey);
56
+ }
57
+ }
@@ -0,0 +1,26 @@
1
+ import { EventPacket, RxNostr } from "rx-nostr";
2
+ import { logger } from "applesauce-core";
3
+ import { CacheRequest, Loader } from "./loader.js";
4
+ export type LoadableEventPointer = {
5
+ id: string;
6
+ /** Relays to load from */
7
+ relays?: string[];
8
+ };
9
+ export type SingleEventLoaderOptions = {
10
+ /**
11
+ * Time interval to buffer requests in ms
12
+ * @default 1000
13
+ */
14
+ bufferTime?: number;
15
+ /** A method used to load events from a local cache */
16
+ cacheRequest?: CacheRequest;
17
+ /**
18
+ * How long the loader should wait before it allows an event pointer to be refreshed from a relay
19
+ * @default 60000
20
+ */
21
+ refreshTimeout?: number;
22
+ };
23
+ export declare class SingleEventLoader extends Loader<LoadableEventPointer, EventPacket> {
24
+ log: typeof logger;
25
+ constructor(rxNostr: RxNostr, opts?: SingleEventLoaderOptions);
26
+ }