applesauce-loaders 1.0.0 → 2.1.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 (66) hide show
  1. package/README.md +204 -65
  2. package/dist/helpers/address-pointer.d.ts +0 -14
  3. package/dist/helpers/address-pointer.js +3 -51
  4. package/dist/helpers/cache.d.ts +7 -0
  5. package/dist/helpers/cache.js +18 -0
  6. package/dist/helpers/event-pointer.js +10 -10
  7. package/dist/helpers/index.d.ts +5 -0
  8. package/dist/helpers/index.js +5 -0
  9. package/dist/helpers/loaders.d.ts +8 -0
  10. package/dist/helpers/loaders.js +42 -0
  11. package/dist/helpers/pointer.d.ts +1 -0
  12. package/dist/helpers/pointer.js +1 -0
  13. package/dist/helpers/upstream.d.ts +7 -0
  14. package/dist/helpers/upstream.js +13 -0
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +2 -1
  17. package/dist/loaders/address-loader.d.ts +48 -0
  18. package/dist/loaders/address-loader.js +121 -0
  19. package/dist/loaders/event-loader.d.ts +36 -0
  20. package/dist/loaders/event-loader.js +90 -0
  21. package/dist/loaders/index.d.ts +8 -8
  22. package/dist/loaders/index.js +8 -8
  23. package/dist/loaders/reactions-loader.d.ts +12 -0
  24. package/dist/loaders/reactions-loader.js +18 -0
  25. package/dist/loaders/social-graph.d.ts +21 -0
  26. package/dist/loaders/social-graph.js +50 -0
  27. package/dist/loaders/tag-value-loader.d.ts +17 -19
  28. package/dist/loaders/tag-value-loader.js +71 -72
  29. package/dist/loaders/timeline-loader.d.ts +23 -21
  30. package/dist/loaders/timeline-loader.js +49 -55
  31. package/dist/loaders/user-lists-loader.d.ts +26 -0
  32. package/dist/loaders/user-lists-loader.js +33 -0
  33. package/dist/loaders/zaps-loader.d.ts +12 -0
  34. package/dist/loaders/zaps-loader.js +18 -0
  35. package/dist/operators/complete-on-eose.d.ts +4 -1
  36. package/dist/operators/complete-on-eose.js +4 -1
  37. package/dist/operators/generator.d.ts +13 -0
  38. package/dist/operators/generator.js +72 -0
  39. package/dist/operators/index.d.ts +1 -1
  40. package/dist/operators/index.js +1 -1
  41. package/dist/types.d.ts +14 -0
  42. package/package.json +6 -4
  43. package/dist/helpers/__tests__/address-pointer.test.js +0 -19
  44. package/dist/loaders/__tests__/dns-identity-loader.test.d.ts +0 -1
  45. package/dist/loaders/__tests__/dns-identity-loader.test.js +0 -59
  46. package/dist/loaders/__tests__/relay-timeline-loader.test.d.ts +0 -1
  47. package/dist/loaders/__tests__/relay-timeline-loader.test.js +0 -26
  48. package/dist/loaders/cache-timeline-loader.d.ts +0 -22
  49. package/dist/loaders/cache-timeline-loader.js +0 -61
  50. package/dist/loaders/loader.d.ts +0 -22
  51. package/dist/loaders/loader.js +0 -22
  52. package/dist/loaders/relay-timeline-loader.d.ts +0 -23
  53. package/dist/loaders/relay-timeline-loader.js +0 -71
  54. package/dist/loaders/replaceable-loader.d.ts +0 -27
  55. package/dist/loaders/replaceable-loader.js +0 -104
  56. package/dist/loaders/single-event-loader.d.ts +0 -32
  57. package/dist/loaders/single-event-loader.js +0 -74
  58. package/dist/loaders/user-sets-loader.d.ts +0 -33
  59. package/dist/loaders/user-sets-loader.js +0 -59
  60. package/dist/operators/__tests__/distinct-relays.test.d.ts +0 -1
  61. package/dist/operators/__tests__/distinct-relays.test.js +0 -75
  62. package/dist/operators/__tests__/generator-sequence.test.d.ts +0 -1
  63. package/dist/operators/__tests__/generator-sequence.test.js +0 -38
  64. package/dist/operators/generator-sequence.d.ts +0 -3
  65. package/dist/operators/generator-sequence.js +0 -53
  66. /package/dist/{helpers/__tests__/address-pointer.test.d.ts → types.js} +0 -0
package/README.md CHANGED
@@ -1,93 +1,232 @@
1
1
  # applesauce-loaders
2
2
 
3
- A collection of loader classes to make loading common events from multiple relays easier.
3
+ A collection of functional loading methods to make common event loading patterns easier.
4
4
 
5
- ## Replaceable event loader
5
+ [Documentation](https://hzrd149.github.io/applesauce/loaders/package.html) [typedoc](https://hzrd149.github.io/applesauce/typedoc/modules/applesauce-loaders.html)
6
6
 
7
- The `ReplaceableLoader` class can be used to load profiles (kind 0), contact lists (kind 3), and any other replaceable (1xxxx) or parameterized replaceable event (3xxxx)
7
+ ## Address Loader
8
+
9
+ The Address Loader is a specialized loader for fetching Nostr replaceable events by their address (kind, pubkey, and optional identifier). It provides an efficient way to batch and deduplicate requests, cache results, and handle relay hints.
8
10
 
9
11
  ```ts
10
- import { Observable } from "rxjs";
12
+ import { createAddressLoader } from "applesauce-loaders/loaders";
11
13
  import { EventStore } from "applesauce-core";
12
- import { ReplaceableLoader } from "applesauce-loaders/loaders";
13
-
14
- export const eventStore = new EventStore();
15
-
16
- // Create a method to let the loaders use nostr-tools relay pool
17
- function nostrRequest(relays: string[], filters: Filter[]) {
18
- return new Observable((observer) => {
19
- const sub = pool.subscribe(filters, {
20
- onevent: (event) => observer.next(event),
21
- oneose: () => {
22
- sub.close();
23
- observer.complete();
24
- },
25
- });
26
-
27
- return () => sub.close();
28
- });
29
- }
14
+ import { RelayPool } from "applesauce-relay";
30
15
 
31
- // create method to load events from the cache relay
32
- function cacheRequest(filters: Filter[]) {
33
- return new Observable((observer) => {
34
- const sub = cacheRelay.subscribe(filters, {
35
- onevent: (event) => observer.next(event),
36
- oneose: () => {
37
- sub.close();
38
- observer.complete();
39
- },
40
- });
41
- });
42
- }
16
+ const eventStore = new EventStore();
17
+ const pool = new RelayPool();
43
18
 
44
- const replaceableLoader = new ReplaceableLoader(rxNostr, {
19
+ // Create an address loader (do this once at the app level)
20
+ const addressLoader = createAddressLoader(pool, {
21
+ // Pass all events to the event store to deduplicate them
22
+ eventStore,
23
+ // Optional configuration options
45
24
  bufferTime: 1000,
46
- // check the cache first for events
47
- cacheRequest: cacheRequest,
48
- // lookup relays are used as a fallback if the event cant be found
49
- lookupRelays: ["wss://purplepag.es/"],
25
+ followRelayHints: true,
26
+ extraRelays: ["wss://relay.example.com"],
50
27
  });
51
28
 
52
- // start the loader by subscribing to it
53
- replaceableLoader.subscribe((packet) => {
54
- // send all loaded events to the event store
55
- eventStore.add(packet.event, packet.from);
29
+ // Load a profile (kind 0)
30
+ addressLoader({
31
+ kind: 0,
32
+ pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
33
+ relays: ["wss://relay.example.com"],
34
+ }).subscribe((event) => {
35
+ // Handle the loaded event
36
+ console.log(event);
56
37
  });
57
38
 
58
- // start loading some replaceable events
59
- replaceableLoader.next({
60
- kind: 0,
39
+ // Load a contact list (kind 3)
40
+ addressLoader({
41
+ kind: 3,
61
42
  pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
62
- relays: ["wss://pyramid.fiatjaf.com/"],
43
+ relays: ["wss://relay.example.com"],
44
+ }).subscribe((event) => {
45
+ // Handle the loaded event
46
+ console.log(event);
63
47
  });
64
48
 
65
- // load a parameterized replaceable event
66
- replaceableLoader.next({
49
+ // Load a parameterized replaceable event
50
+ addressLoader({
67
51
  kind: 30000,
68
52
  pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
69
53
  identifier: "list of bad people",
70
- relays: ["wss://pyramid.fiatjaf.com/"],
54
+ relays: ["wss://relay.example.com"],
55
+ }).subscribe((event) => {
56
+ // Handle the loaded event
57
+ console.log(event);
71
58
  });
59
+ ```
72
60
 
73
- // if no relays are provided only the cache and lookup relays will be checked
74
- replaceableLoader.next({
75
- kind: 3,
76
- pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
61
+ ## Event Loader
62
+
63
+ The Event Loader is a specialized loader for fetching Nostr events by their IDs. It provides an efficient way to batch and deduplicate requests, cache results, and handle relay hints.
64
+
65
+ ```ts
66
+ import { createEventLoader } from "applesauce-loaders/loaders";
67
+
68
+ // Create an event loader (do this once at the app level)
69
+ const eventLoader = createEventLoader(pool, {
70
+ // Pass all events to the event store to deduplicate them
71
+ eventStore,
72
+ // Optional configuration options
73
+ bufferTime: 1000,
74
+ followRelayHints: true,
75
+ extraRelays: ["wss://relay.example.com"],
77
76
  });
78
77
 
79
- // passing a new relay will cause it to be loaded again
80
- replaceableLoader.next({
81
- kind: 0,
82
- pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
83
- relays: ["wss://relay.westernbtc.com/"],
78
+ // Load an event by ID
79
+ eventLoader({
80
+ id: "2650f6292166624f45795248edb9ca136c276a3d10a0d8f4efd2b8b23eb2d5fc",
81
+ relays: ["wss://relay.example.com"],
82
+ }).subscribe((event) => {
83
+ // Handle the loaded event
84
+ console.log(event);
84
85
  });
85
86
 
86
- // or force it to load it again from the same relays
87
- replaceableLoader.next({
88
- kind: 0,
89
- pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
90
- relays: ["wss://pyramid.fiatjaf.com/"],
91
- force: true,
87
+ // Load from extra relays
88
+ eventLoader({
89
+ id: "2650f6292166624f45795248edb9ca136c276a3d10a0d8f4efd2b8b23eb2d5fc",
90
+ relays: ["wss://relay.example.com"],
91
+ }).subscribe((event) => {
92
+ // Handle the loaded event
93
+ console.log(event);
94
+ });
95
+ ```
96
+
97
+ ## Timeline Loader
98
+
99
+ The Timeline Loader is designed for fetching paginated Nostr events in chronological order. It maintains state between calls, allowing you to efficiently load timeline events in blocks until you reach a specific timestamp or exhaust available events.
100
+
101
+ ```ts
102
+ import { createTimelineLoader } from "applesauce-loaders/loaders";
103
+
104
+ // Create a timeline loader
105
+ const timelineLoader = createTimelineLoader(
106
+ pool,
107
+ ["wss://relay.example.com"],
108
+ { kinds: [1] }, // Load text notes
109
+ { eventStore },
110
+ );
111
+
112
+ // Initial load - gets the most recent events
113
+ timelineLoader().subscribe((event) => {
114
+ console.log("Loaded event:", event);
115
+ });
116
+
117
+ // Later, load older events by calling the loader again
118
+ // Each call continues from where the previous one left off
119
+ timelineLoader().subscribe((event) => {
120
+ console.log("Loaded older event:", event);
121
+ });
122
+
123
+ // Load events until a specific timestamp
124
+ const oneWeekAgo = Math.floor(Date.now() / 1000) - 7 * 24 * 60 * 60;
125
+ timelineLoader(oneWeekAgo).subscribe((event) => {
126
+ console.log("Event from last week:", event);
92
127
  });
93
128
  ```
129
+
130
+ ## Loading from cache
131
+
132
+ All loaders support a `cacheRequest` option to load events from a local cache.
133
+
134
+ ```ts
135
+ import { NostrEvent, Filter } from "nostr-tools";
136
+ import { createEventLoader } from "applesauce-loaders/loaders";
137
+
138
+ // Custom method for loading events from a database
139
+ async function cacheRequest(filters: Filter[]): Promise<NostrEvent[]> {
140
+ return await cacheDatabase.getEvents(filters);
141
+ }
142
+
143
+ const eventLoader = createEventLoader(pool, {
144
+ // Pass all events to the event store to deduplicate them
145
+ eventStore,
146
+ // Pass a custom cache method
147
+ cacheRequest,
148
+ // Optional configuration options
149
+ bufferTime: 1000,
150
+ });
151
+
152
+ // Because no relays are specified, the event will be loaded from the cache
153
+ eventLoader({
154
+ id: "2650f6292166624f45795248edb9ca136c276a3d10a0d8f4efd2b8b23eb2d5fc",
155
+ }).subscribe((event) => {
156
+ // Handle the loaded event
157
+ console.log(event);
158
+ });
159
+ ```
160
+
161
+ ## Configuration Options
162
+
163
+ All loaders accept these common configuration options:
164
+
165
+ ### Address Loader Options
166
+
167
+ - `bufferTime`: Time interval to buffer requests in ms (default 1000)
168
+ - `bufferSize`: Max buffer size (default 200)
169
+ - `eventStore`: An event store used to deduplicate events
170
+ - `cacheRequest`: A method used to load events from a local cache
171
+ - `followRelayHints`: Whether to follow relay hints (default true)
172
+ - `lookupRelays`: Fallback lookup relays to check when event can't be found
173
+ - `extraRelays`: An array of relays to always fetch from
174
+
175
+ ### Event Loader Options
176
+
177
+ - `bufferTime`: Time interval to buffer requests in ms (default 1000)
178
+ - `bufferSize`: Max buffer size (default 200)
179
+ - `eventStore`: An event store used to deduplicate events
180
+ - `cacheRequest`: A method used to load events from a local cache
181
+ - `followRelayHints`: Whether to follow relay hints (default true)
182
+ - `extraRelays`: An array of relays to always fetch from
183
+
184
+ ### Timeline Loader Options
185
+
186
+ - `limit`: Maximum number of events to request per filter
187
+ - `cache`: A method used to load events from a local cache
188
+ - `eventStore`: An event store to pass all events to
189
+
190
+ ## Working with Relay Pools
191
+
192
+ All loaders require a request method for loading Nostr events from relays. You can provide this in multiple ways:
193
+
194
+ ### Using a RelayPool instance
195
+
196
+ The simplest approach is to pass a RelayPool instance directly:
197
+
198
+ ```ts
199
+ import { createAddressLoader, createEventLoader } from "applesauce-loaders/loaders";
200
+ import { RelayPool } from "applesauce-relay";
201
+
202
+ const pool = new RelayPool();
203
+ const addressLoader = createAddressLoader(pool, { eventStore });
204
+ const eventLoader = createEventLoader(pool, { eventStore });
205
+ ```
206
+
207
+ ### Using a custom request method
208
+
209
+ You can also provide a custom request method, such as one from nostr-tools:
210
+
211
+ ```ts
212
+ import { createEventLoader } from "applesauce-loaders/loaders";
213
+ import { SimplePool } from "nostr-tools";
214
+ import { Observable } from "rxjs";
215
+
216
+ const pool = SimplePool();
217
+
218
+ // Create a custom request function using nostr-tools
219
+ function customRequest(relays, filters) {
220
+ return new Observable((observer) => {
221
+ const sub = pool.subscribeMany(relays, filters, {
222
+ onevent: (event) => observer.next(event),
223
+ eose: () => observer.complete(),
224
+ });
225
+
226
+ return () => sub.close();
227
+ });
228
+ }
229
+
230
+ // Create event loader with custom request
231
+ const eventLoader = createEventLoader(customRequest, options);
232
+ ```
@@ -1,16 +1,6 @@
1
1
  import { AddressPointerWithoutD } from "applesauce-core/helpers";
2
2
  import { Filter } from "nostr-tools";
3
3
  import { AddressPointer } from "nostr-tools/nip19";
4
- export type LoadableAddressPointer = {
5
- kind: number;
6
- pubkey: string;
7
- /** Optional "d" tag for paramaritized replaceable */
8
- identifier?: string;
9
- /** Relays to load from */
10
- relays?: string[];
11
- /** Load this address pointer even if it has already been loaded */
12
- force?: boolean;
13
- };
14
4
  /** Converts an array of address pointers to a filter */
15
5
  export declare function createFilterFromAddressPointers(pointers: AddressPointerWithoutD[] | AddressPointer[]): Filter;
16
6
  /** Takes a set of address pointers, groups them, then returns filters for the groups */
@@ -23,7 +13,3 @@ export declare function groupAddressPointersByKind(pointers: AddressPointerWitho
23
13
  export declare function groupAddressPointersByPubkey(pointers: AddressPointerWithoutD[]): Map<string, AddressPointerWithoutD[]>;
24
14
  /** Groups address pointers by kind or pubkey depending on which is most optimal */
25
15
  export declare function groupAddressPointersByPubkeyOrKind(pointers: AddressPointerWithoutD[]): Map<string, AddressPointerWithoutD[]> | Map<number, AddressPointerWithoutD[]>;
26
- export declare function getRelaysFromPointers(pointers: AddressPointerWithoutD[]): Set<string>;
27
- export declare function getAddressPointerId<T extends AddressPointerWithoutD>(pointer: T): string;
28
- /** deduplicates an array of address pointers and merges their relays array */
29
- export declare function consolidateAddressPointers(pointers: LoadableAddressPointer[]): LoadableAddressPointer[];
@@ -1,4 +1,3 @@
1
- import { getReplaceableUID, mergeRelaySets } from "applesauce-core/helpers";
2
1
  import { isAddressableKind, isReplaceableKind } from "nostr-tools/kinds";
3
2
  import { unique } from "./array.js";
4
3
  /** Converts an array of address pointers to a filter */
@@ -14,15 +13,15 @@ export function createFilterFromAddressPointers(pointers) {
14
13
  /** Takes a set of address pointers, groups them, then returns filters for the groups */
15
14
  export function createFiltersFromAddressPointers(pointers) {
16
15
  // split the points in to two groups so they they don't mix in the filters
17
- const parameterizedReplaceable = pointers.filter((p) => isAddressableKind(p.kind));
18
16
  const replaceable = pointers.filter((p) => isReplaceableKind(p.kind));
17
+ const addressable = pointers.filter((p) => isAddressableKind(p.kind));
19
18
  const filters = [];
20
19
  if (replaceable.length > 0) {
21
20
  const groups = groupAddressPointersByPubkeyOrKind(replaceable);
22
21
  filters.push(...Array.from(groups.values()).map(createFilterFromAddressPointers));
23
22
  }
24
- if (parameterizedReplaceable.length > 0) {
25
- const groups = groupAddressPointersByPubkeyOrKind(parameterizedReplaceable);
23
+ if (addressable.length > 0) {
24
+ const groups = groupAddressPointersByPubkeyOrKind(addressable);
26
25
  filters.push(...Array.from(groups.values()).map(createFilterFromAddressPointers));
27
26
  }
28
27
  return filters;
@@ -62,50 +61,3 @@ export function groupAddressPointersByPubkeyOrKind(pointers) {
62
61
  const pubkeys = new Set(pointers.map((p) => p.pubkey));
63
62
  return pubkeys.size < kinds.size ? groupAddressPointersByPubkey(pointers) : groupAddressPointersByKind(pointers);
64
63
  }
65
- export function getRelaysFromPointers(pointers) {
66
- const relays = new Set();
67
- for (const pointer of pointers) {
68
- if (!pointer.relays)
69
- continue;
70
- for (const relay of pointer.relays) {
71
- relays.add(relay);
72
- }
73
- }
74
- return relays;
75
- }
76
- export function getAddressPointerId(pointer) {
77
- return getReplaceableUID(pointer.kind, pointer.pubkey, pointer.identifier);
78
- }
79
- /** deep clone a loadable pointer to ensure its safe to modify */
80
- function cloneLoadablePointer(pointer) {
81
- const clone = { ...pointer };
82
- if (pointer.relays)
83
- clone.relays = [...pointer.relays];
84
- return clone;
85
- }
86
- /** deduplicates an array of address pointers and merges their relays array */
87
- export function consolidateAddressPointers(pointers) {
88
- const byId = new Map();
89
- for (const pointer of pointers) {
90
- const id = getAddressPointerId(pointer);
91
- if (byId.has(id)) {
92
- // duplicate, merge pointers
93
- const current = byId.get(id);
94
- // merge relays
95
- if (pointer.relays) {
96
- if (current.relays)
97
- current.relays = mergeRelaySets(current.relays, pointer.relays);
98
- else
99
- current.relays = pointer.relays;
100
- }
101
- // merge force flag
102
- if (pointer.force !== undefined) {
103
- current.force = current.force || pointer.force;
104
- }
105
- }
106
- else
107
- byId.set(id, cloneLoadablePointer(pointer));
108
- }
109
- // return consolidated pointers
110
- return Array.from(byId.values());
111
- }
@@ -0,0 +1,7 @@
1
+ import { Observable } from "rxjs";
2
+ import { Filter, NostrEvent } from "nostr-tools";
3
+ import { CacheRequest } from "../types.js";
4
+ /** Calls the cache request and converts the reponse into an observable */
5
+ export declare function unwrapCacheRequest(request: CacheRequest, filters: Filter[]): Observable<NostrEvent>;
6
+ /** Calls a cache request method with filters and marks all returned events as being from the cache */
7
+ export declare function makeCacheRequest(request: CacheRequest, filters: Filter[]): Observable<NostrEvent>;
@@ -0,0 +1,18 @@
1
+ import { from, isObservable, of, switchMap, tap } from "rxjs";
2
+ import { markFromCache } from "applesauce-core/helpers";
3
+ /** Calls the cache request and converts the reponse into an observable */
4
+ export function unwrapCacheRequest(request, filters) {
5
+ const result = request(filters);
6
+ if (isObservable(result))
7
+ return result;
8
+ else if (result instanceof Promise)
9
+ return from(result).pipe(switchMap((v) => (Array.isArray(v) ? from(v) : of(v))));
10
+ else if (Array.isArray(result))
11
+ return from(result);
12
+ else
13
+ return of(result);
14
+ }
15
+ /** Calls a cache request method with filters and marks all returned events as being from the cache */
16
+ export function makeCacheRequest(request, filters) {
17
+ return unwrapCacheRequest(request, filters).pipe(tap((e) => markFromCache(e)));
18
+ }
@@ -3,17 +3,17 @@ export function consolidateEventPointers(pointers) {
3
3
  let ids = new Map();
4
4
  for (let pointer of pointers) {
5
5
  let existing = ids.get(pointer.id);
6
- if (existing) {
7
- // merge relays
8
- if (pointer.relays) {
9
- if (!existing.relays)
10
- existing.relays = [...pointer.relays];
11
- else
12
- existing.relays = [...existing.relays, ...pointer.relays.filter((r) => !existing.relays.includes(r))];
13
- }
6
+ // merge relays
7
+ if (existing && pointer.relays) {
8
+ if (!existing.relays)
9
+ existing.relays = [...pointer.relays];
10
+ // TODO: maybe use mergeRelaySets here if its performant enough
11
+ else
12
+ existing.relays = [...existing.relays, ...pointer.relays.filter((r) => !existing.relays.includes(r))];
13
+ }
14
+ else if (!existing) {
15
+ ids.set(pointer.id, { ...pointer });
14
16
  }
15
- else
16
- ids.set(pointer.id, pointer);
17
17
  }
18
18
  return Array.from(ids.values());
19
19
  }
@@ -1 +1,6 @@
1
1
  export * from "./dns-identity.js";
2
+ export * from "./cache.js";
3
+ export * from "./event-pointer.js";
4
+ export * from "./address-pointer.js";
5
+ export * from "./loaders.js";
6
+ export * from "./upstream.js";
@@ -1 +1,6 @@
1
1
  export * from "./dns-identity.js";
2
+ export * from "./cache.js";
3
+ export * from "./event-pointer.js";
4
+ export * from "./address-pointer.js";
5
+ export * from "./loaders.js";
6
+ export * from "./upstream.js";
@@ -0,0 +1,8 @@
1
+ import { MonoTypeOperatorFunction, Observable, OperatorFunction } from "rxjs";
2
+ /** Takes a value optionally wrapped in an observable and unwraps it */
3
+ export declare function unwrap<T, R>(value: T | Observable<T>, next: (value: T) => Observable<R>): Observable<R>;
4
+ /**
5
+ * Creates a loader that takes a single value and batches the requests to an upstream loader
6
+ * IMPORTANT: the buffer operator MUST NOT filter values. its important that every input creates a new upstream request
7
+ */
8
+ export declare function batchLoader<Input extends unknown = unknown, Output extends unknown = unknown>(buffer: OperatorFunction<Input, Input[]>, upstream: (input: Input[]) => Observable<Output>, matcher: (input: Input, output: Output) => boolean, output?: MonoTypeOperatorFunction<Output>): (value: Input) => Observable<Output>;
@@ -0,0 +1,42 @@
1
+ import { filter, identity, isObservable, mergeAll, Observable, share, Subject, switchMap, take, } from "rxjs";
2
+ /** Takes a value optionally wrapped in an observable and unwraps it */
3
+ export function unwrap(value, next) {
4
+ return isObservable(value) ? value.pipe(take(1), switchMap(next)) : next(value);
5
+ }
6
+ /**
7
+ * Creates a loader that takes a single value and batches the requests to an upstream loader
8
+ * IMPORTANT: the buffer operator MUST NOT filter values. its important that every input creates a new upstream request
9
+ */
10
+ export function batchLoader(buffer, upstream, matcher, output) {
11
+ const queue = new Subject();
12
+ const next = new Subject();
13
+ // Every "buffer" make a new upstream request
14
+ queue.pipe(buffer).subscribe((buffer) => {
15
+ // If there is nothing in the buffer, dont make a request
16
+ if (buffer.length === 0)
17
+ return;
18
+ return next.next(upstream(buffer).pipe(
19
+ // Never reset the upstream request
20
+ share({ resetOnRefCountZero: false, resetOnComplete: false, resetOnError: false })));
21
+ });
22
+ return (input) => new Observable((observer) => {
23
+ // Add the pointer to the queue when observable is subscribed
24
+ // NOTE: do not use setTimeout here, FF has a strange bug where it will delay the queue.next until after the buffer
25
+ /*
26
+ Adding the value to the queue before the request subscribes to the next batch may cause it to miss the next batch
27
+ but it should work the majority of the time because buffers use setTimeout internally which always runs next tick
28
+ */
29
+ queue.next(input);
30
+ return next
31
+ .pipe(
32
+ // wait for the next batch to run
33
+ take(1),
34
+ // subscribe to it
35
+ mergeAll(),
36
+ // filter the results for the requested input
37
+ filter((output) => matcher(input, output)),
38
+ // Extra output operations
39
+ output ?? identity)
40
+ .subscribe(observer);
41
+ });
42
+ }
@@ -1,3 +1,4 @@
1
+ /** Takes an array of objects with a `relays` property and returns a map of relays to the objects */
1
2
  export declare function groupByRelay<T extends {
2
3
  relays?: string[];
3
4
  }>(pointers: T[], extraRelays?: string[]): Map<string, T[]>;
@@ -1,4 +1,5 @@
1
1
  import { mergeRelaySets } from "applesauce-core/helpers";
2
+ /** Takes an array of objects with a `relays` property and returns a map of relays to the objects */
2
3
  export function groupByRelay(pointers, extraRelays) {
3
4
  let byRelay = new Map();
4
5
  for (const pointer of pointers) {
@@ -0,0 +1,7 @@
1
+ import { Observable } from "rxjs";
2
+ import { NostrRequest, UpstreamPool } from "../types.js";
3
+ import { Filter, NostrEvent } from "nostr-tools";
4
+ /** Makes a nostr request on the upstream pool */
5
+ export declare function makeUpstreamRequest(pool: UpstreamPool, relays: string[], filters: Filter[]): Observable<NostrEvent>;
6
+ /** Wraps an upstream pool and returns a NostrRequest */
7
+ export declare function wrapUpstreamPool(pool: UpstreamPool): NostrRequest;
@@ -0,0 +1,13 @@
1
+ /** Makes a nostr request on the upstream pool */
2
+ export function makeUpstreamRequest(pool, relays, filters) {
3
+ if (typeof pool === "function")
4
+ return pool(relays, filters);
5
+ else if (typeof pool === "object" && "request" in pool)
6
+ return pool.request(relays, filters);
7
+ else
8
+ throw new Error("Invalid upstream pool");
9
+ }
10
+ /** Wraps an upstream pool and returns a NostrRequest */
11
+ export function wrapUpstreamPool(pool) {
12
+ return (relays, filters) => makeUpstreamRequest(pool, relays, filters);
13
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export * from "./loaders/index.js";
1
+ export * as Loaders from "./loaders/index.js";
2
2
  export * as Operators from "./operators/index.js";
3
+ export * from "./types.js";
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- export * from "./loaders/index.js";
1
+ export * as Loaders from "./loaders/index.js";
2
2
  export * as Operators from "./operators/index.js";
3
+ export * from "./types.js";
@@ -0,0 +1,48 @@
1
+ import { IEventStore } from "applesauce-core";
2
+ import { NostrEvent } from "nostr-tools";
3
+ import { Observable } from "rxjs";
4
+ import { CacheRequest, NostrRequest, UpstreamPool } from "../types.js";
5
+ export type LoadableAddressPointer = {
6
+ kind: number;
7
+ pubkey: string;
8
+ /** Optional "d" tag for paramaritized replaceable */
9
+ identifier?: string;
10
+ /** Relays to load from */
11
+ relays?: string[];
12
+ /** Whether to ignore the cache */
13
+ cache?: boolean;
14
+ };
15
+ /** A method that takes address pointers and returns an observable of events */
16
+ export type AddressPointersLoader = (pointers: LoadableAddressPointer[]) => Observable<NostrEvent>;
17
+ export type AddressPointerLoader = (pointer: LoadableAddressPointer) => Observable<NostrEvent>;
18
+ /**
19
+ * Loads address pointers from an async cache
20
+ * @note ignores pointers with force=true
21
+ */
22
+ export declare function cacheAddressPointersLoader(request: CacheRequest): AddressPointersLoader;
23
+ /** Loads address pointers from the relay hints */
24
+ export declare function relayHintsAddressPointersLoader(request: NostrRequest): AddressPointersLoader;
25
+ /** Loads address pointers from an array of relays */
26
+ export declare function relaysAddressPointersLoader(request: NostrRequest, relays: Observable<string[]> | string[]): AddressPointersLoader;
27
+ /** Creates a loader that loads all event pointers based on their relays */
28
+ export declare function addressPointerLoadingSequence(...loaders: (AddressPointersLoader | undefined)[]): AddressPointersLoader;
29
+ /** deduplicates an array of address pointers and merges their relays array */
30
+ export declare function consolidateAddressPointers(pointers: LoadableAddressPointer[]): LoadableAddressPointer[];
31
+ export type AddressLoaderOptions = Partial<{
32
+ /** Time interval to buffer requests in ms ( default 1000 ) */
33
+ bufferTime: number;
34
+ /** Max buffer size ( default 200 ) */
35
+ bufferSize: number;
36
+ /** An event store used to deduplicate events */
37
+ eventStore: IEventStore;
38
+ /** A method used to load events from a local cache */
39
+ cacheRequest: CacheRequest;
40
+ /** Whether to follow relay hints ( default true ) */
41
+ followRelayHints: boolean;
42
+ /** An array of relays to always fetch from */
43
+ extraRelays: string[] | Observable<string[]>;
44
+ /** Fallback lookup relays to check when event cant be found */
45
+ lookupRelays: string[] | Observable<string[]>;
46
+ }>;
47
+ /** Create a pre-built address pointer loader that supports batching, caching, and lookup relays */
48
+ export declare function createAddressLoader(pool: UpstreamPool, opts?: AddressLoaderOptions): AddressPointerLoader;