applesauce-loaders 0.11.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -8
- package/dist/helpers/address-pointer.js +3 -3
- package/dist/helpers/pointer.d.ts +1 -1
- package/dist/helpers/pointer.js +3 -2
- package/dist/loaders/cache-timeline-loader.d.ts +3 -3
- package/dist/loaders/cache-timeline-loader.js +3 -3
- package/dist/loaders/index.d.ts +0 -1
- package/dist/loaders/index.js +0 -1
- package/dist/loaders/loader.d.ts +2 -0
- package/dist/loaders/relay-timeline-loader.d.ts +5 -6
- package/dist/loaders/relay-timeline-loader.js +10 -9
- package/dist/loaders/replaceable-loader.d.ts +8 -4
- package/dist/loaders/replaceable-loader.js +20 -22
- package/dist/loaders/single-event-loader.d.ts +12 -6
- package/dist/loaders/single-event-loader.js +18 -20
- package/dist/loaders/tag-value-loader.d.ts +10 -4
- package/dist/loaders/tag-value-loader.js +19 -18
- package/dist/loaders/timeline-loader.d.ts +6 -6
- package/dist/loaders/timeline-loader.js +5 -5
- package/dist/loaders/user-sets-loader.d.ts +6 -4
- package/dist/loaders/user-sets-loader.js +13 -20
- package/dist/operators/complete-on-eose.d.ts +3 -0
- package/dist/operators/complete-on-eose.js +5 -0
- package/package.json +7 -8
- package/dist/helpers/rx-nostr.d.ts +0 -2
- package/dist/helpers/rx-nostr.js +0 -5
- package/dist/loaders/__tests__/request-loader.test.d.ts +0 -1
- package/dist/loaders/__tests__/request-loader.test.js +0 -37
- package/dist/loaders/request-loader.d.ts +0 -28
- package/dist/loaders/request-loader.js +0 -42
package/README.md
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
# applesauce-loaders
|
|
2
2
|
|
|
3
|
-
A collection of
|
|
3
|
+
A collection of loader classes to make loading common events from multiple relays easier.
|
|
4
4
|
|
|
5
5
|
## Replaceable event loader
|
|
6
6
|
|
|
7
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)
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
+
import { Observable } from "rxjs";
|
|
10
11
|
import { EventStore } from "applesauce-core";
|
|
11
12
|
import { ReplaceableLoader } from "applesauce-loaders/loaders";
|
|
12
|
-
import { createRxNostr, nip07Signer } from "rx-nostr";
|
|
13
|
-
import { verifier } from "rx-nostr-crypto";
|
|
14
13
|
|
|
15
14
|
export const eventStore = new EventStore();
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
}
|
|
22
30
|
|
|
23
31
|
// create method to load events from the cache relay
|
|
24
32
|
function cacheRequest(filters: Filter[]) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getReplaceableUID, mergeRelaySets } from "applesauce-core/helpers";
|
|
2
|
-
import {
|
|
2
|
+
import { isAddressableKind, isReplaceableKind } from "nostr-tools/kinds";
|
|
3
3
|
import { unique } from "./array.js";
|
|
4
4
|
/** Converts an array of address pointers to a filter */
|
|
5
5
|
export function createFilterFromAddressPointers(pointers) {
|
|
@@ -14,7 +14,7 @@ export function createFilterFromAddressPointers(pointers) {
|
|
|
14
14
|
/** Takes a set of address pointers, groups them, then returns filters for the groups */
|
|
15
15
|
export function createFiltersFromAddressPointers(pointers) {
|
|
16
16
|
// split the points in to two groups so they they don't mix in the filters
|
|
17
|
-
const parameterizedReplaceable = pointers.filter((p) =>
|
|
17
|
+
const parameterizedReplaceable = pointers.filter((p) => isAddressableKind(p.kind));
|
|
18
18
|
const replaceable = pointers.filter((p) => isReplaceableKind(p.kind));
|
|
19
19
|
const filters = [];
|
|
20
20
|
if (replaceable.length > 0) {
|
|
@@ -29,7 +29,7 @@ export function createFiltersFromAddressPointers(pointers) {
|
|
|
29
29
|
}
|
|
30
30
|
/** Checks if a relay will understand an address pointer */
|
|
31
31
|
export function isLoadableAddressPointer(pointer) {
|
|
32
|
-
if (
|
|
32
|
+
if (isAddressableKind(pointer.kind))
|
|
33
33
|
return !!pointer.identifier;
|
|
34
34
|
else
|
|
35
35
|
return isReplaceableKind(pointer.kind);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare function groupByRelay<T extends {
|
|
2
2
|
relays?: string[];
|
|
3
|
-
}>(pointers: T[],
|
|
3
|
+
}>(pointers: T[], extraRelays?: string[]): Map<string, T[]>;
|
|
4
4
|
export interface MessageWithRelay {
|
|
5
5
|
relays?: string[];
|
|
6
6
|
/** Ignore timeout and force message through */
|
package/dist/helpers/pointer.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { mergeRelaySets } from "applesauce-core/helpers";
|
|
2
|
+
export function groupByRelay(pointers, extraRelays) {
|
|
2
3
|
let byRelay = new Map();
|
|
3
4
|
for (const pointer of pointers) {
|
|
4
|
-
let relays = pointer.relays
|
|
5
|
+
let relays = mergeRelaySets(pointer.relays, extraRelays);
|
|
5
6
|
for (const relay of relays) {
|
|
6
7
|
if (!byRelay.has(relay))
|
|
7
8
|
byRelay.set(relay, [pointer]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { EventPacket } from "rx-nostr";
|
|
2
|
-
import { BehaviorSubject } from "rxjs";
|
|
3
1
|
import { logger } from "applesauce-core";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
import { BehaviorSubject } from "rxjs";
|
|
4
4
|
import { CacheRequest, Loader } from "./loader.js";
|
|
5
5
|
import { TimelessFilter } from "./relay-timeline-loader.js";
|
|
6
6
|
export type CacheTimelineLoaderOptions = {
|
|
@@ -8,7 +8,7 @@ export type CacheTimelineLoaderOptions = {
|
|
|
8
8
|
limit?: number;
|
|
9
9
|
};
|
|
10
10
|
/** A loader that can be used to load a timeline in chunks */
|
|
11
|
-
export declare class CacheTimelineLoader extends Loader<number | void,
|
|
11
|
+
export declare class CacheTimelineLoader extends Loader<number | void, NostrEvent> {
|
|
12
12
|
filters: TimelessFilter[];
|
|
13
13
|
id: string;
|
|
14
14
|
loading$: BehaviorSubject<boolean>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BehaviorSubject, filter, map, mergeMap, tap } from "rxjs";
|
|
2
|
-
import { markFromCache, unixNow } from "applesauce-core/helpers";
|
|
3
1
|
import { logger } from "applesauce-core";
|
|
2
|
+
import { markFromCache, unixNow } from "applesauce-core/helpers";
|
|
4
3
|
import { nanoid } from "nanoid";
|
|
4
|
+
import { BehaviorSubject, filter, map, mergeMap, tap } from "rxjs";
|
|
5
5
|
import { Loader } from "./loader.js";
|
|
6
6
|
/** A loader that can be used to load a timeline in chunks */
|
|
7
7
|
export class CacheTimelineLoader extends Loader {
|
|
@@ -52,7 +52,7 @@ export class CacheTimelineLoader extends Loader {
|
|
|
52
52
|
this.log(`Finished batch, got ${count} events`);
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
|
-
})
|
|
55
|
+
}));
|
|
56
56
|
})));
|
|
57
57
|
this.filters = filters;
|
|
58
58
|
// create a unique logger for this instance
|
package/dist/loaders/index.d.ts
CHANGED
package/dist/loaders/index.js
CHANGED
package/dist/loaders/loader.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type RelayFilterMap<T = Filter> = {
|
|
|
4
4
|
[relay: string]: T[];
|
|
5
5
|
};
|
|
6
6
|
export type CacheRequest = (filters: Filter[]) => Observable<NostrEvent>;
|
|
7
|
+
export type NostrResponse = NostrEvent | "EOSE";
|
|
8
|
+
export type NostrRequest = (relays: string[], filters: Filter[], id?: string) => Observable<NostrResponse>;
|
|
7
9
|
export interface ILoader<T, R> extends Subscribable<R> {
|
|
8
10
|
next: (value: T) => void;
|
|
9
11
|
pipe: Observable<R>["pipe"];
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { EventPacket, RxNostr } from "rx-nostr";
|
|
2
|
-
import { BehaviorSubject } from "rxjs";
|
|
3
1
|
import { logger } from "applesauce-core";
|
|
4
|
-
import { Filter } from "nostr-tools";
|
|
5
|
-
import {
|
|
2
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
3
|
+
import { BehaviorSubject } from "rxjs";
|
|
4
|
+
import { Loader, NostrRequest } from "./loader.js";
|
|
6
5
|
export type TimelessFilter = Omit<Filter, "since" | "until">;
|
|
7
6
|
export type RelayTimelineLoaderOptions = {
|
|
8
7
|
/** default number of events to request in each batch */
|
|
9
8
|
limit?: number;
|
|
10
9
|
};
|
|
11
10
|
/** A loader that can be used to load a timeline in chunks */
|
|
12
|
-
export declare class RelayTimelineLoader extends Loader<number | void,
|
|
11
|
+
export declare class RelayTimelineLoader extends Loader<number | void, NostrEvent> {
|
|
13
12
|
relay: string;
|
|
14
13
|
filters: TimelessFilter[];
|
|
15
14
|
id: string;
|
|
@@ -20,5 +19,5 @@ export declare class RelayTimelineLoader extends Loader<number | void, EventPack
|
|
|
20
19
|
/** if the timeline is complete */
|
|
21
20
|
complete: boolean;
|
|
22
21
|
protected log: typeof logger;
|
|
23
|
-
constructor(
|
|
22
|
+
constructor(request: NostrRequest, relay: string, filters: TimelessFilter[], opts?: RelayTimelineLoaderOptions);
|
|
24
23
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { createRxOneshotReq } from "rx-nostr";
|
|
2
|
-
import { BehaviorSubject, filter, map, Observable } from "rxjs";
|
|
3
1
|
import { logger } from "applesauce-core";
|
|
4
|
-
import { nanoid } from "nanoid";
|
|
5
2
|
import { unixNow } from "applesauce-core/helpers";
|
|
3
|
+
import { nanoid } from "nanoid";
|
|
4
|
+
import { BehaviorSubject, filter, map, Observable } from "rxjs";
|
|
5
|
+
import { completeOnEOSE } from "../operators/complete-on-eose.js";
|
|
6
6
|
import { Loader } from "./loader.js";
|
|
7
7
|
/** A loader that can be used to load a timeline in chunks */
|
|
8
8
|
export class RelayTimelineLoader extends Loader {
|
|
@@ -18,7 +18,7 @@ export class RelayTimelineLoader extends Loader {
|
|
|
18
18
|
/** if the timeline is complete */
|
|
19
19
|
complete = false;
|
|
20
20
|
log = logger.extend("RelayTimelineLoader");
|
|
21
|
-
constructor(
|
|
21
|
+
constructor(request, relay, filters, opts) {
|
|
22
22
|
super((source) => new Observable((observer) => {
|
|
23
23
|
return source
|
|
24
24
|
.pipe(filter(() => !this.loading && !this.complete), map((limit) => {
|
|
@@ -35,16 +35,17 @@ export class RelayTimelineLoader extends Loader {
|
|
|
35
35
|
.subscribe((filters) => {
|
|
36
36
|
// make batch request
|
|
37
37
|
let count = 0;
|
|
38
|
-
const req = createRxOneshotReq({ filters, rxReqId: this.id });
|
|
39
38
|
this.loading$.next(true);
|
|
40
39
|
this.log(`Next batch starting at ${filters[0].until} limit ${filters[0].limit}`);
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
request([relay], filters)
|
|
41
|
+
.pipe(completeOnEOSE())
|
|
42
|
+
.subscribe({
|
|
43
|
+
next: (event) => {
|
|
43
44
|
// update cursor when event is received
|
|
44
|
-
this.cursor = Math.min(
|
|
45
|
+
this.cursor = Math.min(event.created_at - 1, this.cursor);
|
|
45
46
|
count++;
|
|
46
47
|
// forward packet
|
|
47
|
-
observer.next(
|
|
48
|
+
observer.next(event);
|
|
48
49
|
},
|
|
49
50
|
error: (err) => observer.error(err),
|
|
50
51
|
complete: () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { EventPacket, RxNostr } from "rx-nostr";
|
|
2
1
|
import { logger } from "applesauce-core";
|
|
3
|
-
import {
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
4
3
|
import { LoadableAddressPointer } from "../helpers/address-pointer.js";
|
|
4
|
+
import { CacheRequest, Loader, NostrRequest } from "./loader.js";
|
|
5
5
|
export type ReplaceableLoaderOptions = {
|
|
6
6
|
/**
|
|
7
7
|
* Time interval to buffer requests in ms
|
|
@@ -12,12 +12,16 @@ export type ReplaceableLoaderOptions = {
|
|
|
12
12
|
cacheRequest?: CacheRequest;
|
|
13
13
|
/** Fallback lookup relays to check when event cant be found */
|
|
14
14
|
lookupRelays?: string[];
|
|
15
|
+
/** An array of relays to always fetch from */
|
|
16
|
+
extraRelays?: string[];
|
|
15
17
|
};
|
|
16
|
-
export declare class ReplaceableLoader extends Loader<LoadableAddressPointer,
|
|
18
|
+
export declare class ReplaceableLoader extends Loader<LoadableAddressPointer, NostrEvent> {
|
|
17
19
|
log: typeof logger;
|
|
18
20
|
/** A method used to load events from a local cache */
|
|
19
21
|
cacheRequest?: CacheRequest;
|
|
20
22
|
/** Fallback lookup relays to check when event cant be found */
|
|
21
23
|
lookupRelays?: string[];
|
|
22
|
-
|
|
24
|
+
/** An array of relays to always fetch from */
|
|
25
|
+
extraRelays?: string[];
|
|
26
|
+
constructor(request: NostrRequest, opts?: ReplaceableLoaderOptions);
|
|
23
27
|
}
|
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import { tap, filter, bufferTime, map } from "rxjs";
|
|
2
|
-
import { createRxOneshotReq } from "rx-nostr";
|
|
3
|
-
import { getEventUID, markFromCache, mergeRelaySets } from "applesauce-core/helpers";
|
|
4
1
|
import { logger } from "applesauce-core";
|
|
2
|
+
import { getEventUID, markFromCache, mergeRelaySets } from "applesauce-core/helpers";
|
|
5
3
|
import { nanoid } from "nanoid";
|
|
6
|
-
import {
|
|
7
|
-
import { generatorSequence } from "../operators/generator-sequence.js";
|
|
4
|
+
import { bufferTime, filter, map, tap } from "rxjs";
|
|
8
5
|
import { consolidateAddressPointers, createFiltersFromAddressPointers, getAddressPointerId, getRelaysFromPointers, isLoadableAddressPointer, } from "../helpers/address-pointer.js";
|
|
9
|
-
import {
|
|
6
|
+
import { completeOnEOSE } from "../operators/complete-on-eose.js";
|
|
10
7
|
import { distinctRelaysBatch } from "../operators/distinct-relays.js";
|
|
8
|
+
import { generatorSequence } from "../operators/generator-sequence.js";
|
|
9
|
+
import { Loader } from "./loader.js";
|
|
11
10
|
/** A generator that tries to load the address pointers from the cache first, then tries the relays */
|
|
12
|
-
function* cacheFirstSequence(
|
|
11
|
+
function* cacheFirstSequence(request, pointers, log, opts) {
|
|
13
12
|
const id = nanoid(4);
|
|
14
13
|
let remaining = Array.from(pointers);
|
|
15
14
|
const pointerRelays = Array.from(getRelaysFromPointers(pointers));
|
|
16
15
|
// handle previous step results and decide if to exit
|
|
17
16
|
const handleResults = (results) => {
|
|
18
17
|
if (results.length) {
|
|
19
|
-
const coordinates = new Set(results.map((
|
|
18
|
+
const coordinates = new Set(results.map((event) => getEventUID(event)));
|
|
20
19
|
// if there where results, filter out any pointers that where found
|
|
21
20
|
remaining = remaining.filter((pointer) => {
|
|
22
21
|
const found = coordinates.has(getAddressPointerId(pointer));
|
|
@@ -39,20 +38,16 @@ function* cacheFirstSequence(rxNostr, pointers, log, opts) {
|
|
|
39
38
|
const filters = createFiltersFromAddressPointers(remaining);
|
|
40
39
|
const results = yield opts.cacheRequest(filters).pipe(
|
|
41
40
|
// mark the event as from the cache
|
|
42
|
-
tap((event) => markFromCache(event))
|
|
43
|
-
// convert to event packets
|
|
44
|
-
map((e) => ({ event: e, from: "", subId: "replaceable-loader", type: "EVENT" })));
|
|
41
|
+
tap((event) => markFromCache(event)));
|
|
45
42
|
if (handleResults(results))
|
|
46
43
|
return;
|
|
47
44
|
}
|
|
48
|
-
// load from pointer relays and
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
log(`[${id}] Requesting`, remoteRelays, remaining);
|
|
45
|
+
// load from pointer relays and extra relays
|
|
46
|
+
const mergedRelays = mergeRelaySets(pointerRelays, opts?.extraRelays);
|
|
47
|
+
if (mergedRelays.length > 0) {
|
|
48
|
+
log(`[${id}] Requesting`, mergedRelays, remaining);
|
|
53
49
|
const filters = createFiltersFromAddressPointers(remaining);
|
|
54
|
-
const
|
|
55
|
-
const results = yield rxNostr.use(req, { on: { relays: remoteRelays } });
|
|
50
|
+
const results = yield request(mergedRelays, filters).pipe(completeOnEOSE());
|
|
56
51
|
if (handleResults(results))
|
|
57
52
|
return;
|
|
58
53
|
}
|
|
@@ -63,8 +58,7 @@ function* cacheFirstSequence(rxNostr, pointers, log, opts) {
|
|
|
63
58
|
if (relays.length > 0) {
|
|
64
59
|
log(`[${id}] Request from lookup`, relays, remaining);
|
|
65
60
|
const filters = createFiltersFromAddressPointers(remaining);
|
|
66
|
-
const
|
|
67
|
-
const results = yield rxNostr.use(req, { on: { relays } });
|
|
61
|
+
const results = yield request(relays, filters).pipe(completeOnEOSE());
|
|
68
62
|
if (handleResults(results))
|
|
69
63
|
return;
|
|
70
64
|
}
|
|
@@ -76,7 +70,9 @@ export class ReplaceableLoader extends Loader {
|
|
|
76
70
|
cacheRequest;
|
|
77
71
|
/** Fallback lookup relays to check when event cant be found */
|
|
78
72
|
lookupRelays;
|
|
79
|
-
|
|
73
|
+
/** An array of relays to always fetch from */
|
|
74
|
+
extraRelays;
|
|
75
|
+
constructor(request, opts) {
|
|
80
76
|
super((source) => {
|
|
81
77
|
return source.pipe(
|
|
82
78
|
// filter out invalid pointers
|
|
@@ -92,9 +88,10 @@ export class ReplaceableLoader extends Loader {
|
|
|
92
88
|
// ignore empty buffer
|
|
93
89
|
filter((buffer) => buffer.length > 0),
|
|
94
90
|
// check cache, relays, lookup relays in that order
|
|
95
|
-
generatorSequence((pointers) => cacheFirstSequence(
|
|
91
|
+
generatorSequence((pointers) => cacheFirstSequence(request, pointers, this.log, {
|
|
96
92
|
cacheRequest: this.cacheRequest,
|
|
97
93
|
lookupRelays: this.lookupRelays,
|
|
94
|
+
extraRelays: this.extraRelays,
|
|
98
95
|
}),
|
|
99
96
|
// there will always be more events, never complete
|
|
100
97
|
false));
|
|
@@ -102,5 +99,6 @@ export class ReplaceableLoader extends Loader {
|
|
|
102
99
|
// set options
|
|
103
100
|
this.cacheRequest = opts?.cacheRequest;
|
|
104
101
|
this.lookupRelays = opts?.lookupRelays;
|
|
102
|
+
this.extraRelays = opts?.extraRelays;
|
|
105
103
|
}
|
|
106
104
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { EventPacket, RxNostr } from "rx-nostr";
|
|
2
1
|
import { logger } from "applesauce-core";
|
|
3
|
-
import {
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
import { CacheRequest, Loader, NostrRequest } from "./loader.js";
|
|
4
4
|
export type LoadableEventPointer = {
|
|
5
5
|
id: string;
|
|
6
6
|
/** Relays to load from */
|
|
@@ -12,15 +12,21 @@ export type SingleEventLoaderOptions = {
|
|
|
12
12
|
* @default 1000
|
|
13
13
|
*/
|
|
14
14
|
bufferTime?: number;
|
|
15
|
-
/** A method used to load events from a local cache */
|
|
16
|
-
cacheRequest?: CacheRequest;
|
|
17
15
|
/**
|
|
18
16
|
* How long the loader should wait before it allows an event pointer to be refreshed from a relay
|
|
19
17
|
* @default 60000
|
|
20
18
|
*/
|
|
21
19
|
refreshTimeout?: number;
|
|
20
|
+
/** A method used to load events from a local cache */
|
|
21
|
+
cacheRequest?: CacheRequest;
|
|
22
|
+
/** An array of relays to always fetch from */
|
|
23
|
+
extraRelays?: string[];
|
|
22
24
|
};
|
|
23
|
-
export declare class SingleEventLoader extends Loader<LoadableEventPointer,
|
|
25
|
+
export declare class SingleEventLoader extends Loader<LoadableEventPointer, NostrEvent> {
|
|
24
26
|
log: typeof logger;
|
|
25
|
-
|
|
27
|
+
/** A method used to load events from a local cache */
|
|
28
|
+
cacheRequest?: CacheRequest;
|
|
29
|
+
/** An array of relays to always fetch from */
|
|
30
|
+
extraRelays?: string[];
|
|
31
|
+
constructor(request: NostrRequest, opts?: SingleEventLoaderOptions);
|
|
26
32
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { bufferTime, filter, from, map, mergeAll, tap } from "rxjs";
|
|
2
|
-
import { createRxOneshotReq } from "rx-nostr";
|
|
3
2
|
import { markFromCache } from "applesauce-core/helpers";
|
|
4
3
|
import { logger } from "applesauce-core";
|
|
5
4
|
import { nanoid } from "nanoid";
|
|
@@ -8,21 +7,20 @@ import { generatorSequence } from "../operators/generator-sequence.js";
|
|
|
8
7
|
import { distinctRelaysBatch } from "../operators/distinct-relays.js";
|
|
9
8
|
import { groupByRelay } from "../helpers/pointer.js";
|
|
10
9
|
import { consolidateEventPointers } from "../helpers/event-pointer.js";
|
|
11
|
-
|
|
10
|
+
import { completeOnEOSE } from "../operators/complete-on-eose.js";
|
|
11
|
+
function* cacheFirstSequence(request, pointers, opts, log) {
|
|
12
12
|
let remaining = [...pointers];
|
|
13
13
|
const id = nanoid(8);
|
|
14
14
|
log = log.extend(id);
|
|
15
15
|
const loaded = (packets) => {
|
|
16
|
-
const ids = new Set(packets.map((p) => p.
|
|
16
|
+
const ids = new Set(packets.map((p) => p.id));
|
|
17
17
|
remaining = remaining.filter((p) => !ids.has(p.id));
|
|
18
18
|
};
|
|
19
19
|
if (opts?.cacheRequest) {
|
|
20
20
|
let filter = { ids: remaining.map((e) => e.id) };
|
|
21
21
|
const results = yield opts.cacheRequest([filter]).pipe(
|
|
22
22
|
// mark the event as from the cache
|
|
23
|
-
tap((event) => markFromCache(event))
|
|
24
|
-
// convert to event packets
|
|
25
|
-
map((e) => ({ event: e, from: "", subId: "single-event-loader", type: "EVENT" })));
|
|
23
|
+
tap((event) => markFromCache(event)));
|
|
26
24
|
if (results.length > 0) {
|
|
27
25
|
log(`Loaded ${results.length} events from cache`);
|
|
28
26
|
loaded(results);
|
|
@@ -31,20 +29,15 @@ function* cacheFirstSequence(rxNostr, pointers, opts, log) {
|
|
|
31
29
|
// exit early if all pointers are loaded
|
|
32
30
|
if (remaining.length === 0)
|
|
33
31
|
return;
|
|
34
|
-
let byRelay = groupByRelay(remaining,
|
|
32
|
+
let byRelay = groupByRelay(remaining, opts.extraRelays);
|
|
35
33
|
// load remaining pointers from the relays
|
|
36
34
|
let results = yield from(Array.from(byRelay.entries()).map(([relay, pointers]) => {
|
|
37
35
|
let filter = { ids: pointers.map((e) => e.id) };
|
|
38
36
|
let count = 0;
|
|
39
|
-
const req = createRxOneshotReq({ filters: [filter], rxReqId: id });
|
|
40
37
|
log(`Requesting from ${relay}`, filter.ids);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
sub$ = rxNostr.use(req);
|
|
45
|
-
else
|
|
46
|
-
sub$ = rxNostr.use(req, { on: { relays: [relay] } });
|
|
47
|
-
return sub$.pipe(tap({
|
|
38
|
+
return request([relay], [filter], id)
|
|
39
|
+
.pipe(completeOnEOSE())
|
|
40
|
+
.pipe(tap({
|
|
48
41
|
next: () => count++,
|
|
49
42
|
complete: () => log(`Completed ${relay}, loaded ${count} events`),
|
|
50
43
|
}));
|
|
@@ -57,20 +50,25 @@ function* cacheFirstSequence(rxNostr, pointers, opts, log) {
|
|
|
57
50
|
}
|
|
58
51
|
export class SingleEventLoader extends Loader {
|
|
59
52
|
log = logger.extend("SingleEventLoader");
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
/** A method used to load events from a local cache */
|
|
54
|
+
cacheRequest;
|
|
55
|
+
/** An array of relays to always fetch from */
|
|
56
|
+
extraRelays;
|
|
57
|
+
constructor(request, opts) {
|
|
62
58
|
super((source) => source.pipe(
|
|
63
|
-
//
|
|
59
|
+
// batch every second
|
|
64
60
|
bufferTime(opts?.bufferTime ?? 1000),
|
|
65
61
|
// ignore empty buffers
|
|
66
62
|
filter((buffer) => buffer.length > 0),
|
|
67
63
|
// only request events from relays once
|
|
68
|
-
distinctRelaysBatch((p) => p.id,
|
|
64
|
+
distinctRelaysBatch((p) => p.id, opts?.refreshTimeout ?? 60_000),
|
|
69
65
|
// ensure there is only one of each event pointer
|
|
70
66
|
map(consolidateEventPointers),
|
|
71
67
|
// run the loader sequence
|
|
72
|
-
generatorSequence((pointers) => cacheFirstSequence(
|
|
68
|
+
generatorSequence((pointers) => cacheFirstSequence(request, pointers, { cacheRequest: this.cacheRequest, extraRelays: this.extraRelays }, this.log),
|
|
73
69
|
// there will always be more events, never complete
|
|
74
70
|
false)));
|
|
71
|
+
this.cacheRequest = opts?.cacheRequest;
|
|
72
|
+
this.extraRelays = opts?.extraRelays;
|
|
75
73
|
}
|
|
76
74
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { EventPacket, RxNostr } from "rx-nostr";
|
|
2
1
|
import { logger } from "applesauce-core";
|
|
3
|
-
import {
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
import { CacheRequest, Loader, NostrRequest } from "./loader.js";
|
|
4
4
|
export type TabValuePointer = {
|
|
5
5
|
/** The value of the tag to load */
|
|
6
6
|
value: string;
|
|
@@ -25,9 +25,15 @@ export type TagValueLoaderOptions = {
|
|
|
25
25
|
since?: number;
|
|
26
26
|
/** Method used to load from the cache */
|
|
27
27
|
cacheRequest?: CacheRequest;
|
|
28
|
+
/** An array of relays to always fetch from */
|
|
29
|
+
extraRelays?: string[];
|
|
28
30
|
};
|
|
29
|
-
export declare class TagValueLoader extends Loader<TabValuePointer,
|
|
31
|
+
export declare class TagValueLoader extends Loader<TabValuePointer, NostrEvent> {
|
|
30
32
|
name: string;
|
|
31
33
|
protected log: typeof logger;
|
|
32
|
-
|
|
34
|
+
/** A method to load events from a local cache */
|
|
35
|
+
cacheRequest?: CacheRequest;
|
|
36
|
+
/** An array of relays to always fetch from */
|
|
37
|
+
extraRelays?: string[];
|
|
38
|
+
constructor(request: NostrRequest, tagName: string, opts?: TagValueLoaderOptions);
|
|
33
39
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { createRxOneshotReq } from "rx-nostr";
|
|
2
|
-
import { bufferTime, filter, map, merge, mergeMap, tap } from "rxjs";
|
|
3
|
-
import { markFromCache } from "applesauce-core/helpers";
|
|
4
1
|
import { logger } from "applesauce-core";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { getDefaultReadRelays } from "../helpers/rx-nostr.js";
|
|
2
|
+
import { markFromCache, mergeRelaySets } from "applesauce-core/helpers";
|
|
3
|
+
import { bufferTime, filter, merge, mergeMap, tap } from "rxjs";
|
|
8
4
|
import { unique } from "../helpers/array.js";
|
|
5
|
+
import { completeOnEOSE } from "../operators/complete-on-eose.js";
|
|
6
|
+
import { distinctRelaysBatch } from "../operators/distinct-relays.js";
|
|
7
|
+
import { Loader } from "./loader.js";
|
|
9
8
|
export class TagValueLoader extends Loader {
|
|
10
9
|
name;
|
|
11
10
|
log = logger.extend("TagValueLoader");
|
|
12
|
-
|
|
11
|
+
/** A method to load events from a local cache */
|
|
12
|
+
cacheRequest;
|
|
13
|
+
/** An array of relays to always fetch from */
|
|
14
|
+
extraRelays;
|
|
15
|
+
constructor(request, tagName, opts) {
|
|
13
16
|
const filterTag = `#${tagName}`;
|
|
14
17
|
super((source) => source.pipe(
|
|
15
18
|
// batch the pointers
|
|
@@ -29,7 +32,7 @@ export class TagValueLoader extends Loader {
|
|
|
29
32
|
baseFilter.authors = opts.authors;
|
|
30
33
|
// build request map for relays
|
|
31
34
|
const requestMap = pointers.reduce((map, pointer) => {
|
|
32
|
-
const relays = pointer.relays
|
|
35
|
+
const relays = mergeRelaySets(pointer.relays, this.extraRelays);
|
|
33
36
|
for (const relay of relays) {
|
|
34
37
|
if (!map[relay]) {
|
|
35
38
|
// create new filter for relay
|
|
@@ -45,9 +48,9 @@ export class TagValueLoader extends Loader {
|
|
|
45
48
|
return map;
|
|
46
49
|
}, {});
|
|
47
50
|
let fromCache = 0;
|
|
48
|
-
const cacheRequest =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
const cacheRequest = this?.cacheRequest?.([
|
|
52
|
+
{ ...baseFilter, [filterTag]: unique(pointers.map((p) => p.value)) },
|
|
53
|
+
]).pipe(
|
|
51
54
|
// mark the event as from the cache
|
|
52
55
|
tap({
|
|
53
56
|
next: (event) => {
|
|
@@ -58,16 +61,14 @@ export class TagValueLoader extends Loader {
|
|
|
58
61
|
if (fromCache > 0)
|
|
59
62
|
this.log(`Loaded ${fromCache} from cache`);
|
|
60
63
|
},
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
map((e) => ({ event: e, from: "", subId: "replaceable-loader", type: "EVENT" })));
|
|
64
|
-
const requests = Object.entries(requestMap).map(([relay, filters]) => {
|
|
65
|
-
const req = createRxOneshotReq({ filters });
|
|
66
|
-
return rxNostr.use(req, { on: { relays: [relay] } });
|
|
67
|
-
});
|
|
64
|
+
}));
|
|
65
|
+
const requests = Object.entries(requestMap).map(([relay, filters]) => request([relay], filters).pipe(completeOnEOSE()));
|
|
68
66
|
this.log(`Requesting ${pointers.length} tag values from ${requests.length} relays`);
|
|
69
67
|
return cacheRequest ? merge(cacheRequest, ...requests) : merge(...requests);
|
|
70
68
|
})));
|
|
69
|
+
// Set options
|
|
70
|
+
this.cacheRequest = opts?.cacheRequest;
|
|
71
|
+
this.extraRelays = opts?.extraRelays;
|
|
71
72
|
// create a unique logger for this instance
|
|
72
73
|
this.name = opts?.name ?? "";
|
|
73
74
|
this.log = this.log.extend(opts?.kinds ? `${this.name} ${filterTag} (${opts?.kinds?.join(",")})` : `${this.name} ${filterTag}`);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { EventPacket, RxNostr } from "rx-nostr";
|
|
2
|
-
import { BehaviorSubject } from "rxjs";
|
|
3
1
|
import { logger } from "applesauce-core";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
import { BehaviorSubject } from "rxjs";
|
|
6
4
|
import { CacheTimelineLoader } from "./cache-timeline-loader.js";
|
|
5
|
+
import { CacheRequest, Loader, NostrRequest, RelayFilterMap } from "./loader.js";
|
|
6
|
+
import { RelayTimelineLoader, TimelessFilter } from "./relay-timeline-loader.js";
|
|
7
7
|
export type TimelineLoaderOptions = {
|
|
8
8
|
limit?: number;
|
|
9
9
|
cacheRequest?: CacheRequest;
|
|
10
10
|
};
|
|
11
11
|
/** A multi-relay timeline loader that can be used to load a timeline from multiple relays */
|
|
12
|
-
export declare class TimelineLoader extends Loader<number | undefined,
|
|
12
|
+
export declare class TimelineLoader extends Loader<number | undefined, NostrEvent> {
|
|
13
13
|
id: string;
|
|
14
14
|
loading$: BehaviorSubject<boolean>;
|
|
15
15
|
get loading(): boolean;
|
|
@@ -17,6 +17,6 @@ export declare class TimelineLoader extends Loader<number | undefined, EventPack
|
|
|
17
17
|
protected log: typeof logger;
|
|
18
18
|
protected cache?: CacheTimelineLoader;
|
|
19
19
|
protected loaders: Map<string, RelayTimelineLoader>;
|
|
20
|
-
constructor(
|
|
20
|
+
constructor(request: NostrRequest, requests: RelayFilterMap<TimelessFilter>, opts?: TimelineLoaderOptions);
|
|
21
21
|
static simpleFilterMap(relays: string[], filters: TimelessFilter[]): RelayFilterMap<TimelessFilter>;
|
|
22
22
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { BehaviorSubject, combineLatest, connect, merge, tap } from "rxjs";
|
|
2
1
|
import { logger } from "applesauce-core";
|
|
3
2
|
import { mergeFilters } from "applesauce-core/helpers";
|
|
4
3
|
import { nanoid } from "nanoid";
|
|
5
|
-
import {
|
|
6
|
-
import { Loader } from "./loader.js";
|
|
4
|
+
import { BehaviorSubject, combineLatest, connect, merge, tap } from "rxjs";
|
|
7
5
|
import { CacheTimelineLoader } from "./cache-timeline-loader.js";
|
|
6
|
+
import { Loader } from "./loader.js";
|
|
7
|
+
import { RelayTimelineLoader } from "./relay-timeline-loader.js";
|
|
8
8
|
/** A multi-relay timeline loader that can be used to load a timeline from multiple relays */
|
|
9
9
|
export class TimelineLoader extends Loader {
|
|
10
10
|
id = nanoid(8);
|
|
@@ -16,7 +16,7 @@ export class TimelineLoader extends Loader {
|
|
|
16
16
|
log = logger.extend("TimelineLoader");
|
|
17
17
|
cache;
|
|
18
18
|
loaders;
|
|
19
|
-
constructor(
|
|
19
|
+
constructor(request, requests, opts) {
|
|
20
20
|
const loaders = new Map();
|
|
21
21
|
// create cache loader
|
|
22
22
|
const cache = opts?.cacheRequest
|
|
@@ -24,7 +24,7 @@ export class TimelineLoader extends Loader {
|
|
|
24
24
|
: undefined;
|
|
25
25
|
// create loaders
|
|
26
26
|
for (const [relay, filters] of Object.entries(requests)) {
|
|
27
|
-
loaders.set(relay, new RelayTimelineLoader(
|
|
27
|
+
loaders.set(relay, new RelayTimelineLoader(request, relay, filters, opts));
|
|
28
28
|
}
|
|
29
29
|
const allLoaders = cache ? [cache, ...loaders.values()] : Array.from(loaders.values());
|
|
30
30
|
super((source) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { EventPacket, RxNostr } from "rx-nostr";
|
|
2
1
|
import { logger } from "applesauce-core";
|
|
3
|
-
import {
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
import { CacheRequest, Loader, NostrRequest } from "./loader.js";
|
|
4
4
|
export type LoadableSetPointer = {
|
|
5
5
|
/** A replaceable kind >= 30000 & < 40000 */
|
|
6
6
|
kind: number;
|
|
@@ -25,7 +25,9 @@ export type UserSetsLoaderOptions = {
|
|
|
25
25
|
refreshTimeout?: number;
|
|
26
26
|
};
|
|
27
27
|
/** A loader that can be used to load users NIP-51 sets events ( kind >= 30000 < 40000) */
|
|
28
|
-
export declare class UserSetsLoader extends Loader<LoadableSetPointer,
|
|
28
|
+
export declare class UserSetsLoader extends Loader<LoadableSetPointer, NostrEvent> {
|
|
29
29
|
log: typeof logger;
|
|
30
|
-
|
|
30
|
+
/** An array of relays to always fetch from */
|
|
31
|
+
extraRelays?: string[];
|
|
32
|
+
constructor(request: NostrRequest, opts?: UserSetsLoaderOptions);
|
|
31
33
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { tap, from, filter, map, mergeAll, bufferTime } from "rxjs";
|
|
2
|
-
import { createRxOneshotReq } from "rx-nostr";
|
|
3
|
-
import { markFromCache } from "applesauce-core/helpers";
|
|
4
1
|
import { logger } from "applesauce-core";
|
|
2
|
+
import { markFromCache } from "applesauce-core/helpers";
|
|
5
3
|
import { nanoid } from "nanoid";
|
|
6
|
-
import {
|
|
7
|
-
import { generatorSequence } from "../operators/generator-sequence.js";
|
|
4
|
+
import { bufferTime, filter, from, map, mergeAll, tap } from "rxjs";
|
|
8
5
|
import { consolidateAddressPointers, createFiltersFromAddressPointers } from "../helpers/address-pointer.js";
|
|
9
6
|
import { groupByRelay } from "../helpers/pointer.js";
|
|
7
|
+
import { completeOnEOSE } from "../operators/complete-on-eose.js";
|
|
10
8
|
import { distinctRelaysBatch } from "../operators/distinct-relays.js";
|
|
9
|
+
import { generatorSequence } from "../operators/generator-sequence.js";
|
|
10
|
+
import { Loader } from "./loader.js";
|
|
11
11
|
/** A generator that tries to load the address pointers from the cache first, then tries the relays */
|
|
12
|
-
function* cacheFirstSequence(
|
|
12
|
+
function* cacheFirstSequence(request, pointers, log, opts) {
|
|
13
13
|
const id = nanoid(8);
|
|
14
14
|
log = log.extend(id);
|
|
15
15
|
// first attempt, load from cache relays
|
|
@@ -18,27 +18,18 @@ function* cacheFirstSequence(rxNostr, pointers, log, opts) {
|
|
|
18
18
|
const filters = createFiltersFromAddressPointers(pointers);
|
|
19
19
|
const results = yield opts.cacheRequest(filters).pipe(
|
|
20
20
|
// mark the event as from the cache
|
|
21
|
-
tap((event) => markFromCache(event))
|
|
22
|
-
// convert to event packets
|
|
23
|
-
map((e) => ({ event: e, from: "", subId: "user-sets-loader", type: "EVENT" })));
|
|
21
|
+
tap((event) => markFromCache(event)));
|
|
24
22
|
if (results.length > 0) {
|
|
25
23
|
log(`Loaded ${results.length} events from cache`);
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
|
-
let byRelay = groupByRelay(pointers,
|
|
26
|
+
let byRelay = groupByRelay(pointers, opts?.extraRelays);
|
|
29
27
|
// load sets from relays
|
|
30
28
|
yield from(Array.from(byRelay.entries()).map(([relay, pointers]) => {
|
|
31
29
|
let filters = createFiltersFromAddressPointers(pointers);
|
|
32
30
|
let count = 0;
|
|
33
|
-
const req = createRxOneshotReq({ filters, rxReqId: id });
|
|
34
31
|
log(`Requesting from ${relay}`, pointers);
|
|
35
|
-
|
|
36
|
-
// don't specify relay if this is the "default" relay
|
|
37
|
-
if (relay === "default")
|
|
38
|
-
sub$ = rxNostr.use(req);
|
|
39
|
-
else
|
|
40
|
-
sub$ = rxNostr.use(req, { on: { relays: [relay] } });
|
|
41
|
-
return sub$.pipe(tap({
|
|
32
|
+
return request([relay], filters, id).pipe(completeOnEOSE(), tap({
|
|
42
33
|
next: () => count++,
|
|
43
34
|
complete: () => log(`Completed ${relay}, loaded ${count} events`),
|
|
44
35
|
}));
|
|
@@ -47,7 +38,9 @@ function* cacheFirstSequence(rxNostr, pointers, log, opts) {
|
|
|
47
38
|
/** A loader that can be used to load users NIP-51 sets events ( kind >= 30000 < 40000) */
|
|
48
39
|
export class UserSetsLoader extends Loader {
|
|
49
40
|
log = logger.extend("UserSetsLoader");
|
|
50
|
-
|
|
41
|
+
/** An array of relays to always fetch from */
|
|
42
|
+
extraRelays;
|
|
43
|
+
constructor(request, opts) {
|
|
51
44
|
let options = opts || {};
|
|
52
45
|
super((source) => source.pipe(
|
|
53
46
|
// load first from cache
|
|
@@ -59,7 +52,7 @@ export class UserSetsLoader extends Loader {
|
|
|
59
52
|
// deduplicate address pointers
|
|
60
53
|
map(consolidateAddressPointers),
|
|
61
54
|
// check cache, relays, lookup relays in that order
|
|
62
|
-
generatorSequence((pointers) => cacheFirstSequence(
|
|
55
|
+
generatorSequence((pointers) => cacheFirstSequence(request, pointers, this.log, options),
|
|
63
56
|
// there will always be more events, never complete
|
|
64
57
|
false)));
|
|
65
58
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-loaders",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A collection of observable based loaders built on rx-nostr",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"keywords": [
|
|
9
|
-
"nostr"
|
|
9
|
+
"nostr",
|
|
10
|
+
"applesauce"
|
|
10
11
|
],
|
|
11
12
|
"author": "hzrd149",
|
|
12
13
|
"license": "MIT",
|
|
@@ -52,18 +53,16 @@
|
|
|
52
53
|
}
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
55
|
-
"applesauce-core": "^0.
|
|
56
|
+
"applesauce-core": "^1.0.0",
|
|
56
57
|
"nanoid": "^5.0.9",
|
|
57
58
|
"nostr-tools": "^2.10.4",
|
|
58
|
-
"rx-nostr": "^3.5.0",
|
|
59
59
|
"rxjs": "^7.8.1"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"vitest": "^3.0.5",
|
|
62
|
+
"typescript": "^5.8.3",
|
|
63
|
+
"vitest": "^3.1.1",
|
|
65
64
|
"vitest-nostr": "^0.4.1",
|
|
66
|
-
"vitest-websocket-mock": "^0.
|
|
65
|
+
"vitest-websocket-mock": "^0.5.0"
|
|
67
66
|
},
|
|
68
67
|
"funding": {
|
|
69
68
|
"type": "lightning",
|
package/dist/helpers/rx-nostr.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Subject } from "rxjs";
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import { TimeoutError } from "applesauce-core/observable";
|
|
4
|
-
import { EventStore, QueryStore } from "applesauce-core";
|
|
5
|
-
import { RequestLoader } from "../request-loader.js";
|
|
6
|
-
let eventStore;
|
|
7
|
-
let queryStore;
|
|
8
|
-
let loader;
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
eventStore = new EventStore();
|
|
11
|
-
queryStore = new QueryStore(eventStore);
|
|
12
|
-
loader = new RequestLoader(queryStore);
|
|
13
|
-
// @ts-expect-error
|
|
14
|
-
loader.replaceableLoader = new Subject();
|
|
15
|
-
vi.spyOn(loader.replaceableLoader, "next").mockImplementation(() => { });
|
|
16
|
-
});
|
|
17
|
-
describe("profile", () => {
|
|
18
|
-
it("should return a promise that resolves", async () => {
|
|
19
|
-
const p = loader.profile({ pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" });
|
|
20
|
-
expect(loader.replaceableLoader.next).toHaveBeenCalledWith(expect.objectContaining({ pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" }));
|
|
21
|
-
eventStore.add({
|
|
22
|
-
content: '{"name":"fiatjaf","about":"~","picture":"https://fiatjaf.com/static/favicon.jpg","nip05":"_@fiatjaf.com","lud16":"fiatjaf@zbd.gg","website":"https://nostr.technology"}',
|
|
23
|
-
created_at: 1738588530,
|
|
24
|
-
id: "c43be8b4634298e97dde3020a5e6aeec37d7f5a4b0259705f496e81a550c8f8b",
|
|
25
|
-
kind: 0,
|
|
26
|
-
pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
|
27
|
-
sig: "202a1bf6a58943d660c1891662dbdda142aa8e5bca9d4a3cb03cde816ad3bdda6f4ec3b880671506c2820285b32218a0afdec2d172de9694d83972190ab4f9da",
|
|
28
|
-
tags: [],
|
|
29
|
-
});
|
|
30
|
-
expect(await p).toEqual(expect.objectContaining({ name: "fiatjaf" }));
|
|
31
|
-
});
|
|
32
|
-
it("should reject with TimeoutError after 10 seconds", async () => {
|
|
33
|
-
// reduce timeout for tests
|
|
34
|
-
loader.requestTimeout = 10;
|
|
35
|
-
await expect(loader.profile({ pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" })).rejects.toThrow(TimeoutError);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { EventStore, 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: EventStore, 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
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { kinds } from "nostr-tools";
|
|
2
|
-
import { MailboxesQuery, ProfileQuery, ReplaceableQuery, UserContactsQuery } from "applesauce-core/queries";
|
|
3
|
-
import { getObservableValue, simpleTimeout } from "applesauce-core/observable";
|
|
4
|
-
import { filter } from "rxjs";
|
|
5
|
-
/** A special Promised based loader built on the {@link QueryStore} */
|
|
6
|
-
export class RequestLoader {
|
|
7
|
-
store;
|
|
8
|
-
requestTimeout = 10_000;
|
|
9
|
-
replaceableLoader;
|
|
10
|
-
constructor(store) {
|
|
11
|
-
this.store = store;
|
|
12
|
-
}
|
|
13
|
-
// hacky method to run queries with timeouts
|
|
14
|
-
async runWithTimeout(queryConstructor, ...args) {
|
|
15
|
-
return getObservableValue(this.store.createQuery(queryConstructor, ...args).pipe(filter((v) => v !== undefined && v !== null), simpleTimeout(this.requestTimeout)));
|
|
16
|
-
}
|
|
17
|
-
checkReplaceable() {
|
|
18
|
-
if (!this.replaceableLoader)
|
|
19
|
-
throw new Error("Missing ReplaceableLoader");
|
|
20
|
-
return this.replaceableLoader;
|
|
21
|
-
}
|
|
22
|
-
/** Requests a single replaceable event */
|
|
23
|
-
replaceable(pointer, force) {
|
|
24
|
-
this.checkReplaceable().next({ ...pointer, force });
|
|
25
|
-
return this.runWithTimeout(ReplaceableQuery, pointer.kind, pointer.pubkey, pointer.identifier);
|
|
26
|
-
}
|
|
27
|
-
/** Loads a pubkeys profile */
|
|
28
|
-
profile(pointer, force) {
|
|
29
|
-
this.checkReplaceable().next({ kind: kinds.Metadata, pubkey: pointer.pubkey, relays: pointer.relays, force });
|
|
30
|
-
return this.runWithTimeout(ProfileQuery, pointer.pubkey);
|
|
31
|
-
}
|
|
32
|
-
/** Loads a pubkeys profile */
|
|
33
|
-
mailboxes(pointer, force) {
|
|
34
|
-
this.checkReplaceable().next({ kind: kinds.RelayList, pubkey: pointer.pubkey, relays: pointer.relays, force });
|
|
35
|
-
return this.runWithTimeout(MailboxesQuery, pointer.pubkey);
|
|
36
|
-
}
|
|
37
|
-
/** Loads a pubkeys profile */
|
|
38
|
-
contacts(pointer, force) {
|
|
39
|
-
this.checkReplaceable().next({ kind: kinds.Contacts, pubkey: pointer.pubkey, relays: pointer.relays, force });
|
|
40
|
-
return this.runWithTimeout(UserContactsQuery, pointer.pubkey);
|
|
41
|
-
}
|
|
42
|
-
}
|