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.
- package/README.md +204 -65
- package/dist/helpers/address-pointer.d.ts +0 -14
- package/dist/helpers/address-pointer.js +3 -51
- package/dist/helpers/cache.d.ts +7 -0
- package/dist/helpers/cache.js +18 -0
- package/dist/helpers/event-pointer.js +10 -10
- package/dist/helpers/index.d.ts +5 -0
- package/dist/helpers/index.js +5 -0
- package/dist/helpers/loaders.d.ts +8 -0
- package/dist/helpers/loaders.js +42 -0
- package/dist/helpers/pointer.d.ts +1 -0
- package/dist/helpers/pointer.js +1 -0
- package/dist/helpers/upstream.d.ts +7 -0
- package/dist/helpers/upstream.js +13 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/loaders/address-loader.d.ts +48 -0
- package/dist/loaders/address-loader.js +121 -0
- package/dist/loaders/event-loader.d.ts +36 -0
- package/dist/loaders/event-loader.js +90 -0
- package/dist/loaders/index.d.ts +8 -8
- package/dist/loaders/index.js +8 -8
- package/dist/loaders/reactions-loader.d.ts +12 -0
- package/dist/loaders/reactions-loader.js +18 -0
- package/dist/loaders/social-graph.d.ts +21 -0
- package/dist/loaders/social-graph.js +50 -0
- package/dist/loaders/tag-value-loader.d.ts +17 -19
- package/dist/loaders/tag-value-loader.js +71 -72
- package/dist/loaders/timeline-loader.d.ts +23 -21
- package/dist/loaders/timeline-loader.js +49 -55
- package/dist/loaders/user-lists-loader.d.ts +26 -0
- package/dist/loaders/user-lists-loader.js +33 -0
- package/dist/loaders/zaps-loader.d.ts +12 -0
- package/dist/loaders/zaps-loader.js +18 -0
- package/dist/operators/complete-on-eose.d.ts +4 -1
- package/dist/operators/complete-on-eose.js +4 -1
- package/dist/operators/generator.d.ts +13 -0
- package/dist/operators/generator.js +72 -0
- package/dist/operators/index.d.ts +1 -1
- package/dist/operators/index.js +1 -1
- package/dist/types.d.ts +14 -0
- package/package.json +6 -4
- package/dist/helpers/__tests__/address-pointer.test.js +0 -19
- package/dist/loaders/__tests__/dns-identity-loader.test.d.ts +0 -1
- package/dist/loaders/__tests__/dns-identity-loader.test.js +0 -59
- package/dist/loaders/__tests__/relay-timeline-loader.test.d.ts +0 -1
- package/dist/loaders/__tests__/relay-timeline-loader.test.js +0 -26
- package/dist/loaders/cache-timeline-loader.d.ts +0 -22
- package/dist/loaders/cache-timeline-loader.js +0 -61
- package/dist/loaders/loader.d.ts +0 -22
- package/dist/loaders/loader.js +0 -22
- package/dist/loaders/relay-timeline-loader.d.ts +0 -23
- package/dist/loaders/relay-timeline-loader.js +0 -71
- package/dist/loaders/replaceable-loader.d.ts +0 -27
- package/dist/loaders/replaceable-loader.js +0 -104
- package/dist/loaders/single-event-loader.d.ts +0 -32
- package/dist/loaders/single-event-loader.js +0 -74
- package/dist/loaders/user-sets-loader.d.ts +0 -33
- package/dist/loaders/user-sets-loader.js +0 -59
- package/dist/operators/__tests__/distinct-relays.test.d.ts +0 -1
- package/dist/operators/__tests__/distinct-relays.test.js +0 -75
- package/dist/operators/__tests__/generator-sequence.test.d.ts +0 -1
- package/dist/operators/__tests__/generator-sequence.test.js +0 -38
- package/dist/operators/generator-sequence.d.ts +0 -3
- package/dist/operators/generator-sequence.js +0 -53
- /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
|
|
3
|
+
A collection of functional loading methods to make common event loading patterns easier.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[Documentation](https://hzrd149.github.io/applesauce/loaders/package.html) [typedoc](https://hzrd149.github.io/applesauce/typedoc/modules/applesauce-loaders.html)
|
|
6
6
|
|
|
7
|
-
|
|
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 {
|
|
12
|
+
import { createAddressLoader } from "applesauce-loaders/loaders";
|
|
11
13
|
import { EventStore } from "applesauce-core";
|
|
12
|
-
import {
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
//
|
|
59
|
-
|
|
60
|
-
kind:
|
|
39
|
+
// Load a contact list (kind 3)
|
|
40
|
+
addressLoader({
|
|
41
|
+
kind: 3,
|
|
61
42
|
pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
|
62
|
-
relays: ["wss://
|
|
43
|
+
relays: ["wss://relay.example.com"],
|
|
44
|
+
}).subscribe((event) => {
|
|
45
|
+
// Handle the loaded event
|
|
46
|
+
console.log(event);
|
|
63
47
|
});
|
|
64
48
|
|
|
65
|
-
//
|
|
66
|
-
|
|
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://
|
|
54
|
+
relays: ["wss://relay.example.com"],
|
|
55
|
+
}).subscribe((event) => {
|
|
56
|
+
// Handle the loaded event
|
|
57
|
+
console.log(event);
|
|
71
58
|
});
|
|
59
|
+
```
|
|
72
60
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 (
|
|
25
|
-
const groups = groupAddressPointersByPubkeyOrKind(
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
}
|
package/dist/helpers/index.d.ts
CHANGED
package/dist/helpers/index.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/helpers/pointer.js
CHANGED
|
@@ -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
package/dist/index.js
CHANGED
|
@@ -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;
|