applesauce-loaders 0.12.0 → 2.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 +204 -57
- package/dist/helpers/address-pointer.d.ts +2 -2
- package/dist/helpers/address-pointer.js +11 -13
- package/dist/helpers/cache.d.ts +9 -0
- package/dist/helpers/cache.js +22 -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 +3 -0
- package/dist/helpers/loaders.js +27 -0
- package/dist/helpers/pointer.d.ts +2 -1
- package/dist/helpers/pointer.js +4 -2
- 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 +37 -0
- package/dist/loaders/address-loader.js +94 -0
- package/dist/loaders/event-loader.d.ts +33 -0
- package/dist/loaders/event-loader.js +92 -0
- package/dist/loaders/index.d.ts +8 -9
- package/dist/loaders/index.js +8 -9
- 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 +19 -15
- package/dist/loaders/tag-value-loader.js +73 -71
- package/dist/loaders/timeline-loader.d.ts +24 -22
- 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 +6 -0
- package/dist/operators/complete-on-eose.js +8 -0
- 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 +8 -8
- package/dist/helpers/__tests__/address-pointer.test.js +0 -19
- package/dist/helpers/rx-nostr.d.ts +0 -2
- package/dist/helpers/rx-nostr.js +0 -5
- 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/__tests__/request-loader.test.d.ts +0 -1
- package/dist/loaders/__tests__/request-loader.test.js +0 -37
- 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 -20
- package/dist/loaders/loader.js +0 -22
- package/dist/loaders/relay-timeline-loader.d.ts +0 -24
- package/dist/loaders/relay-timeline-loader.js +0 -70
- package/dist/loaders/replaceable-loader.d.ts +0 -23
- package/dist/loaders/replaceable-loader.js +0 -106
- package/dist/loaders/request-loader.d.ts +0 -30
- package/dist/loaders/request-loader.js +0 -57
- package/dist/loaders/single-event-loader.d.ts +0 -26
- package/dist/loaders/single-event-loader.js +0 -76
- package/dist/loaders/user-sets-loader.d.ts +0 -31
- package/dist/loaders/user-sets-loader.js +0 -66
- 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/dist/loaders/loader.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
-
import { InteropObservable, Observable, OperatorFunction, Subject, Subscribable } from "rxjs";
|
|
3
|
-
export type RelayFilterMap<T = Filter> = {
|
|
4
|
-
[relay: string]: T[];
|
|
5
|
-
};
|
|
6
|
-
export type CacheRequest = (filters: Filter[]) => Observable<NostrEvent>;
|
|
7
|
-
export interface ILoader<T, R> extends Subscribable<R> {
|
|
8
|
-
next: (value: T) => void;
|
|
9
|
-
pipe: Observable<R>["pipe"];
|
|
10
|
-
}
|
|
11
|
-
/** Base loader class */
|
|
12
|
-
export declare class Loader<Input, Output> implements ILoader<Input, Output>, InteropObservable<Output> {
|
|
13
|
-
protected subject: Subject<Input>;
|
|
14
|
-
observable: Observable<Output>;
|
|
15
|
-
pipe: Observable<Output>["pipe"];
|
|
16
|
-
subscribe: Observable<Output>["subscribe"];
|
|
17
|
-
constructor(transform: OperatorFunction<Input, Output>);
|
|
18
|
-
next(value: Input): void;
|
|
19
|
-
[Symbol.observable](): Observable<Output>;
|
|
20
|
-
}
|
package/dist/loaders/loader.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { share, Subject } from "rxjs";
|
|
2
|
-
/** Base loader class */
|
|
3
|
-
export class Loader {
|
|
4
|
-
subject = new Subject();
|
|
5
|
-
observable;
|
|
6
|
-
pipe;
|
|
7
|
-
subscribe;
|
|
8
|
-
constructor(transform) {
|
|
9
|
-
this.observable = this.subject.pipe(transform,
|
|
10
|
-
// only create a single instance of the transformer
|
|
11
|
-
share());
|
|
12
|
-
// copy pipe function
|
|
13
|
-
this.pipe = this.observable.pipe.bind(this.observable);
|
|
14
|
-
this.subscribe = this.observable.subscribe.bind(this.observable);
|
|
15
|
-
}
|
|
16
|
-
next(value) {
|
|
17
|
-
this.subject.next(value);
|
|
18
|
-
}
|
|
19
|
-
[Symbol.observable]() {
|
|
20
|
-
return this.observable;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { EventPacket, RxNostr } from "rx-nostr";
|
|
2
|
-
import { BehaviorSubject } from "rxjs";
|
|
3
|
-
import { logger } from "applesauce-core";
|
|
4
|
-
import { Filter } from "nostr-tools";
|
|
5
|
-
import { Loader } from "./loader.js";
|
|
6
|
-
export type TimelessFilter = Omit<Filter, "since" | "until">;
|
|
7
|
-
export type RelayTimelineLoaderOptions = {
|
|
8
|
-
/** default number of events to request in each batch */
|
|
9
|
-
limit?: number;
|
|
10
|
-
};
|
|
11
|
-
/** A loader that can be used to load a timeline in chunks */
|
|
12
|
-
export declare class RelayTimelineLoader extends Loader<number | void, EventPacket> {
|
|
13
|
-
relay: string;
|
|
14
|
-
filters: TimelessFilter[];
|
|
15
|
-
id: string;
|
|
16
|
-
loading$: BehaviorSubject<boolean>;
|
|
17
|
-
get loading(): boolean;
|
|
18
|
-
/** current "until" timestamp */
|
|
19
|
-
cursor: number;
|
|
20
|
-
/** if the timeline is complete */
|
|
21
|
-
complete: boolean;
|
|
22
|
-
protected log: typeof logger;
|
|
23
|
-
constructor(rxNostr: RxNostr, relay: string, filters: TimelessFilter[], opts?: RelayTimelineLoaderOptions);
|
|
24
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { createRxOneshotReq } from "rx-nostr";
|
|
2
|
-
import { BehaviorSubject, filter, map, Observable } from "rxjs";
|
|
3
|
-
import { logger } from "applesauce-core";
|
|
4
|
-
import { nanoid } from "nanoid";
|
|
5
|
-
import { unixNow } from "applesauce-core/helpers";
|
|
6
|
-
import { Loader } from "./loader.js";
|
|
7
|
-
/** A loader that can be used to load a timeline in chunks */
|
|
8
|
-
export class RelayTimelineLoader extends Loader {
|
|
9
|
-
relay;
|
|
10
|
-
filters;
|
|
11
|
-
id = nanoid(8);
|
|
12
|
-
loading$ = new BehaviorSubject(false);
|
|
13
|
-
get loading() {
|
|
14
|
-
return this.loading$.value;
|
|
15
|
-
}
|
|
16
|
-
/** current "until" timestamp */
|
|
17
|
-
cursor = Infinity;
|
|
18
|
-
/** if the timeline is complete */
|
|
19
|
-
complete = false;
|
|
20
|
-
log = logger.extend("RelayTimelineLoader");
|
|
21
|
-
constructor(rxNostr, relay, filters, opts) {
|
|
22
|
-
super((source) => new Observable((observer) => {
|
|
23
|
-
return source
|
|
24
|
-
.pipe(filter(() => !this.loading && !this.complete), map((limit) => {
|
|
25
|
-
// build next batch filters
|
|
26
|
-
return filters.map((filter) => ({
|
|
27
|
-
limit: limit || opts?.limit,
|
|
28
|
-
...filter,
|
|
29
|
-
// limit curser to now
|
|
30
|
-
until: Math.min(unixNow(), this.cursor),
|
|
31
|
-
}));
|
|
32
|
-
}),
|
|
33
|
-
// ignore empty filters
|
|
34
|
-
filter((filters) => filters.length > 0))
|
|
35
|
-
.subscribe((filters) => {
|
|
36
|
-
// make batch request
|
|
37
|
-
let count = 0;
|
|
38
|
-
const req = createRxOneshotReq({ filters, rxReqId: this.id });
|
|
39
|
-
this.loading$.next(true);
|
|
40
|
-
this.log(`Next batch starting at ${filters[0].until} limit ${filters[0].limit}`);
|
|
41
|
-
rxNostr.use(req, { on: { relays: [relay] } }).subscribe({
|
|
42
|
-
next: (packet) => {
|
|
43
|
-
// update cursor when event is received
|
|
44
|
-
this.cursor = Math.min(packet.event.created_at - 1, this.cursor);
|
|
45
|
-
count++;
|
|
46
|
-
// forward packet
|
|
47
|
-
observer.next(packet);
|
|
48
|
-
},
|
|
49
|
-
error: (err) => observer.error(err),
|
|
50
|
-
complete: () => {
|
|
51
|
-
// set loading to false when batch completes
|
|
52
|
-
this.loading$.next(false);
|
|
53
|
-
// set complete the observable if 0 events where returned
|
|
54
|
-
if (count === 0) {
|
|
55
|
-
observer.complete();
|
|
56
|
-
this.log(`Got ${count} event, Complete`);
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
this.log(`Finished batch, got ${count} events`);
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
}));
|
|
65
|
-
this.relay = relay;
|
|
66
|
-
this.filters = filters;
|
|
67
|
-
// create a unique logger for this instance
|
|
68
|
-
this.log = this.log.extend(relay);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { EventPacket, RxNostr } from "rx-nostr";
|
|
2
|
-
import { logger } from "applesauce-core";
|
|
3
|
-
import { CacheRequest, Loader } from "./loader.js";
|
|
4
|
-
import { LoadableAddressPointer } from "../helpers/address-pointer.js";
|
|
5
|
-
export type ReplaceableLoaderOptions = {
|
|
6
|
-
/**
|
|
7
|
-
* Time interval to buffer requests in ms
|
|
8
|
-
* @default 1000
|
|
9
|
-
*/
|
|
10
|
-
bufferTime?: number;
|
|
11
|
-
/** A method used to load events from a local cache */
|
|
12
|
-
cacheRequest?: CacheRequest;
|
|
13
|
-
/** Fallback lookup relays to check when event cant be found */
|
|
14
|
-
lookupRelays?: string[];
|
|
15
|
-
};
|
|
16
|
-
export declare class ReplaceableLoader extends Loader<LoadableAddressPointer, EventPacket> {
|
|
17
|
-
log: typeof logger;
|
|
18
|
-
/** A method used to load events from a local cache */
|
|
19
|
-
cacheRequest?: CacheRequest;
|
|
20
|
-
/** Fallback lookup relays to check when event cant be found */
|
|
21
|
-
lookupRelays?: string[];
|
|
22
|
-
constructor(rxNostr: RxNostr, opts?: ReplaceableLoaderOptions);
|
|
23
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { tap, filter, bufferTime, map } from "rxjs";
|
|
2
|
-
import { createRxOneshotReq } from "rx-nostr";
|
|
3
|
-
import { getEventUID, markFromCache, mergeRelaySets } from "applesauce-core/helpers";
|
|
4
|
-
import { logger } from "applesauce-core";
|
|
5
|
-
import { nanoid } from "nanoid";
|
|
6
|
-
import { Loader } from "./loader.js";
|
|
7
|
-
import { generatorSequence } from "../operators/generator-sequence.js";
|
|
8
|
-
import { consolidateAddressPointers, createFiltersFromAddressPointers, getAddressPointerId, getRelaysFromPointers, isLoadableAddressPointer, } from "../helpers/address-pointer.js";
|
|
9
|
-
import { getDefaultReadRelays } from "../helpers/rx-nostr.js";
|
|
10
|
-
import { distinctRelaysBatch } from "../operators/distinct-relays.js";
|
|
11
|
-
/** A generator that tries to load the address pointers from the cache first, then tries the relays */
|
|
12
|
-
function* cacheFirstSequence(rxNostr, pointers, log, opts) {
|
|
13
|
-
const id = nanoid(4);
|
|
14
|
-
let remaining = Array.from(pointers);
|
|
15
|
-
const pointerRelays = Array.from(getRelaysFromPointers(pointers));
|
|
16
|
-
// handle previous step results and decide if to exit
|
|
17
|
-
const handleResults = (results) => {
|
|
18
|
-
if (results.length) {
|
|
19
|
-
const coordinates = new Set(results.map((p) => getEventUID(p.event)));
|
|
20
|
-
// if there where results, filter out any pointers that where found
|
|
21
|
-
remaining = remaining.filter((pointer) => {
|
|
22
|
-
const found = coordinates.has(getAddressPointerId(pointer));
|
|
23
|
-
if (found && pointer.force !== true)
|
|
24
|
-
return false;
|
|
25
|
-
else
|
|
26
|
-
return true;
|
|
27
|
-
});
|
|
28
|
-
// If there are none left, complete
|
|
29
|
-
if (remaining.length === 0) {
|
|
30
|
-
log(`[${id}] Complete`);
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return false;
|
|
35
|
-
};
|
|
36
|
-
// first attempt, load from cache relays
|
|
37
|
-
if (opts?.cacheRequest) {
|
|
38
|
-
log(`[${id}] Checking cache`, remaining);
|
|
39
|
-
const filters = createFiltersFromAddressPointers(remaining);
|
|
40
|
-
const results = yield opts.cacheRequest(filters).pipe(
|
|
41
|
-
// 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" })));
|
|
45
|
-
if (handleResults(results))
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
// load from pointer relays and default relays
|
|
49
|
-
const defaultRelays = getDefaultReadRelays(rxNostr);
|
|
50
|
-
const remoteRelays = mergeRelaySets(pointerRelays, defaultRelays);
|
|
51
|
-
if (remoteRelays.length > 0) {
|
|
52
|
-
log(`[${id}] Requesting`, remoteRelays, remaining);
|
|
53
|
-
const filters = createFiltersFromAddressPointers(remaining);
|
|
54
|
-
const req = createRxOneshotReq({ filters, rxReqId: id });
|
|
55
|
-
const results = yield rxNostr.use(req, { on: { relays: remoteRelays } });
|
|
56
|
-
if (handleResults(results))
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
// finally load from lookup relays
|
|
60
|
-
if (opts?.lookupRelays) {
|
|
61
|
-
// make sure we aren't asking a relay twice
|
|
62
|
-
const relays = opts.lookupRelays.filter((r) => !pointerRelays.includes(r));
|
|
63
|
-
if (relays.length > 0) {
|
|
64
|
-
log(`[${id}] Request from lookup`, relays, remaining);
|
|
65
|
-
const filters = createFiltersFromAddressPointers(remaining);
|
|
66
|
-
const req = createRxOneshotReq({ filters, rxReqId: id });
|
|
67
|
-
const results = yield rxNostr.use(req, { on: { relays } });
|
|
68
|
-
if (handleResults(results))
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
export class ReplaceableLoader extends Loader {
|
|
74
|
-
log = logger.extend("ReplaceableLoader");
|
|
75
|
-
/** A method used to load events from a local cache */
|
|
76
|
-
cacheRequest;
|
|
77
|
-
/** Fallback lookup relays to check when event cant be found */
|
|
78
|
-
lookupRelays;
|
|
79
|
-
constructor(rxNostr, opts) {
|
|
80
|
-
super((source) => {
|
|
81
|
-
return source.pipe(
|
|
82
|
-
// filter out invalid pointers
|
|
83
|
-
filter(isLoadableAddressPointer),
|
|
84
|
-
// buffer on time
|
|
85
|
-
bufferTime(opts?.bufferTime ?? 1000),
|
|
86
|
-
// ignore empty buffers
|
|
87
|
-
filter((buffer) => buffer.length > 0),
|
|
88
|
-
// only fetch from each relay once
|
|
89
|
-
distinctRelaysBatch(getAddressPointerId),
|
|
90
|
-
// consolidate buffered pointers
|
|
91
|
-
map(consolidateAddressPointers),
|
|
92
|
-
// ignore empty buffer
|
|
93
|
-
filter((buffer) => buffer.length > 0),
|
|
94
|
-
// check cache, relays, lookup relays in that order
|
|
95
|
-
generatorSequence((pointers) => cacheFirstSequence(rxNostr, pointers, this.log, {
|
|
96
|
-
cacheRequest: this.cacheRequest,
|
|
97
|
-
lookupRelays: this.lookupRelays,
|
|
98
|
-
}),
|
|
99
|
-
// there will always be more events, never complete
|
|
100
|
-
false));
|
|
101
|
-
});
|
|
102
|
-
// set options
|
|
103
|
-
this.cacheRequest = opts?.cacheRequest;
|
|
104
|
-
this.lookupRelays = opts?.lookupRelays;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { IEventStore, QueryStore } from "applesauce-core";
|
|
2
|
-
import { ProfilePointer } from "nostr-tools/nip19";
|
|
3
|
-
import { Observable } from "rxjs";
|
|
4
|
-
import { ReplaceableLoader } from "./replaceable-loader.js";
|
|
5
|
-
import { LoadableAddressPointer } from "../helpers/address-pointer.js";
|
|
6
|
-
/** A special Promised based loader built on the {@link QueryStore} */
|
|
7
|
-
export declare class RequestLoader {
|
|
8
|
-
store: QueryStore;
|
|
9
|
-
requestTimeout: number;
|
|
10
|
-
replaceableLoader?: ReplaceableLoader;
|
|
11
|
-
constructor(store: QueryStore);
|
|
12
|
-
protected runWithTimeout<T extends unknown, Args extends Array<any>>(queryConstructor: (...args: Args) => {
|
|
13
|
-
key: string;
|
|
14
|
-
run: (events: IEventStore, store: QueryStore) => Observable<T>;
|
|
15
|
-
}, ...args: Args): Promise<NonNullable<T>>;
|
|
16
|
-
protected checkReplaceable(): ReplaceableLoader;
|
|
17
|
-
/** Requests a single replaceable event */
|
|
18
|
-
replaceable(pointer: LoadableAddressPointer, force?: boolean): Promise<import("nostr-tools").Event>;
|
|
19
|
-
/** Loads a pubkeys profile */
|
|
20
|
-
profile(pointer: ProfilePointer, force?: boolean): Promise<import("applesauce-core/helpers").ProfileContent>;
|
|
21
|
-
/** Loads a pubkeys profile */
|
|
22
|
-
mailboxes(pointer: ProfilePointer, force?: boolean): Promise<{
|
|
23
|
-
inboxes: string[];
|
|
24
|
-
outboxes: string[];
|
|
25
|
-
}>;
|
|
26
|
-
/** Loads a pubkeys profile */
|
|
27
|
-
contacts(pointer: ProfilePointer, force?: boolean): Promise<ProfilePointer[]>;
|
|
28
|
-
/** Loads a pubkeys blossom servers */
|
|
29
|
-
blossomServers(pointer: ProfilePointer, force?: boolean): Promise<URL[]>;
|
|
30
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { kinds } from "nostr-tools";
|
|
2
|
-
import { MailboxesQuery, ProfileQuery, ReplaceableQuery, UserBlossomServersQuery, UserContactsQuery, } from "applesauce-core/queries";
|
|
3
|
-
import { getObservableValue, simpleTimeout } from "applesauce-core/observable";
|
|
4
|
-
import { filter } from "rxjs";
|
|
5
|
-
import { BLOSSOM_SERVER_LIST_KIND } from "applesauce-core/helpers";
|
|
6
|
-
/** A special Promised based loader built on the {@link QueryStore} */
|
|
7
|
-
export class RequestLoader {
|
|
8
|
-
store;
|
|
9
|
-
requestTimeout = 10_000;
|
|
10
|
-
replaceableLoader;
|
|
11
|
-
constructor(store) {
|
|
12
|
-
this.store = store;
|
|
13
|
-
}
|
|
14
|
-
// hacky method to run queries with timeouts
|
|
15
|
-
async runWithTimeout(queryConstructor, ...args) {
|
|
16
|
-
return getObservableValue(this.store.createQuery(queryConstructor, ...args).pipe(
|
|
17
|
-
// ignore undefined and null values
|
|
18
|
-
filter((v) => v !== undefined && v !== null),
|
|
19
|
-
// timeout with an error is not values
|
|
20
|
-
simpleTimeout(this.requestTimeout)));
|
|
21
|
-
}
|
|
22
|
-
checkReplaceable() {
|
|
23
|
-
if (!this.replaceableLoader)
|
|
24
|
-
throw new Error("Missing ReplaceableLoader");
|
|
25
|
-
return this.replaceableLoader;
|
|
26
|
-
}
|
|
27
|
-
/** Requests a single replaceable event */
|
|
28
|
-
replaceable(pointer, force) {
|
|
29
|
-
this.checkReplaceable().next({ ...pointer, force });
|
|
30
|
-
return this.runWithTimeout(ReplaceableQuery, pointer.kind, pointer.pubkey, pointer.identifier);
|
|
31
|
-
}
|
|
32
|
-
/** Loads a pubkeys profile */
|
|
33
|
-
profile(pointer, force) {
|
|
34
|
-
this.checkReplaceable().next({ kind: kinds.Metadata, pubkey: pointer.pubkey, relays: pointer.relays, force });
|
|
35
|
-
return this.runWithTimeout(ProfileQuery, pointer.pubkey);
|
|
36
|
-
}
|
|
37
|
-
/** Loads a pubkeys profile */
|
|
38
|
-
mailboxes(pointer, force) {
|
|
39
|
-
this.checkReplaceable().next({ kind: kinds.RelayList, pubkey: pointer.pubkey, relays: pointer.relays, force });
|
|
40
|
-
return this.runWithTimeout(MailboxesQuery, pointer.pubkey);
|
|
41
|
-
}
|
|
42
|
-
/** Loads a pubkeys profile */
|
|
43
|
-
contacts(pointer, force) {
|
|
44
|
-
this.checkReplaceable().next({ kind: kinds.Contacts, pubkey: pointer.pubkey, relays: pointer.relays, force });
|
|
45
|
-
return this.runWithTimeout(UserContactsQuery, pointer.pubkey);
|
|
46
|
-
}
|
|
47
|
-
/** Loads a pubkeys blossom servers */
|
|
48
|
-
blossomServers(pointer, force) {
|
|
49
|
-
this.checkReplaceable().next({
|
|
50
|
-
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
51
|
-
pubkey: pointer.pubkey,
|
|
52
|
-
relays: pointer.relays,
|
|
53
|
-
force,
|
|
54
|
-
});
|
|
55
|
-
return this.runWithTimeout(UserBlossomServersQuery, pointer.pubkey);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { EventPacket, RxNostr } from "rx-nostr";
|
|
2
|
-
import { logger } from "applesauce-core";
|
|
3
|
-
import { CacheRequest, Loader } from "./loader.js";
|
|
4
|
-
export type LoadableEventPointer = {
|
|
5
|
-
id: string;
|
|
6
|
-
/** Relays to load from */
|
|
7
|
-
relays?: string[];
|
|
8
|
-
};
|
|
9
|
-
export type SingleEventLoaderOptions = {
|
|
10
|
-
/**
|
|
11
|
-
* Time interval to buffer requests in ms
|
|
12
|
-
* @default 1000
|
|
13
|
-
*/
|
|
14
|
-
bufferTime?: number;
|
|
15
|
-
/** A method used to load events from a local cache */
|
|
16
|
-
cacheRequest?: CacheRequest;
|
|
17
|
-
/**
|
|
18
|
-
* How long the loader should wait before it allows an event pointer to be refreshed from a relay
|
|
19
|
-
* @default 60000
|
|
20
|
-
*/
|
|
21
|
-
refreshTimeout?: number;
|
|
22
|
-
};
|
|
23
|
-
export declare class SingleEventLoader extends Loader<LoadableEventPointer, EventPacket> {
|
|
24
|
-
log: typeof logger;
|
|
25
|
-
constructor(rxNostr: RxNostr, opts?: SingleEventLoaderOptions);
|
|
26
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { bufferTime, filter, from, map, mergeAll, tap } from "rxjs";
|
|
2
|
-
import { createRxOneshotReq } from "rx-nostr";
|
|
3
|
-
import { markFromCache } from "applesauce-core/helpers";
|
|
4
|
-
import { logger } from "applesauce-core";
|
|
5
|
-
import { nanoid } from "nanoid";
|
|
6
|
-
import { Loader } from "./loader.js";
|
|
7
|
-
import { generatorSequence } from "../operators/generator-sequence.js";
|
|
8
|
-
import { distinctRelaysBatch } from "../operators/distinct-relays.js";
|
|
9
|
-
import { groupByRelay } from "../helpers/pointer.js";
|
|
10
|
-
import { consolidateEventPointers } from "../helpers/event-pointer.js";
|
|
11
|
-
function* cacheFirstSequence(rxNostr, pointers, opts, log) {
|
|
12
|
-
let remaining = [...pointers];
|
|
13
|
-
const id = nanoid(8);
|
|
14
|
-
log = log.extend(id);
|
|
15
|
-
const loaded = (packets) => {
|
|
16
|
-
const ids = new Set(packets.map((p) => p.event.id));
|
|
17
|
-
remaining = remaining.filter((p) => !ids.has(p.id));
|
|
18
|
-
};
|
|
19
|
-
if (opts?.cacheRequest) {
|
|
20
|
-
let filter = { ids: remaining.map((e) => e.id) };
|
|
21
|
-
const results = yield opts.cacheRequest([filter]).pipe(
|
|
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" })));
|
|
26
|
-
if (results.length > 0) {
|
|
27
|
-
log(`Loaded ${results.length} events from cache`);
|
|
28
|
-
loaded(results);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// exit early if all pointers are loaded
|
|
32
|
-
if (remaining.length === 0)
|
|
33
|
-
return;
|
|
34
|
-
let byRelay = groupByRelay(remaining, "default");
|
|
35
|
-
// load remaining pointers from the relays
|
|
36
|
-
let results = yield from(Array.from(byRelay.entries()).map(([relay, pointers]) => {
|
|
37
|
-
let filter = { ids: pointers.map((e) => e.id) };
|
|
38
|
-
let count = 0;
|
|
39
|
-
const req = createRxOneshotReq({ filters: [filter], rxReqId: id });
|
|
40
|
-
log(`Requesting from ${relay}`, filter.ids);
|
|
41
|
-
let sub$;
|
|
42
|
-
// don't specify relay if this is the "default" relay
|
|
43
|
-
if (relay === "default")
|
|
44
|
-
sub$ = rxNostr.use(req);
|
|
45
|
-
else
|
|
46
|
-
sub$ = rxNostr.use(req, { on: { relays: [relay] } });
|
|
47
|
-
return sub$.pipe(tap({
|
|
48
|
-
next: () => count++,
|
|
49
|
-
complete: () => log(`Completed ${relay}, loaded ${count} events`),
|
|
50
|
-
}));
|
|
51
|
-
})).pipe(mergeAll());
|
|
52
|
-
loaded(results);
|
|
53
|
-
if (remaining.length > 0) {
|
|
54
|
-
// failed to find remaining
|
|
55
|
-
log("Failed to load", remaining.map((p) => p.id));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
export class SingleEventLoader extends Loader {
|
|
59
|
-
log = logger.extend("SingleEventLoader");
|
|
60
|
-
constructor(rxNostr, opts) {
|
|
61
|
-
let options = opts || {};
|
|
62
|
-
super((source) => source.pipe(
|
|
63
|
-
// load first from cache
|
|
64
|
-
bufferTime(opts?.bufferTime ?? 1000),
|
|
65
|
-
// ignore empty buffers
|
|
66
|
-
filter((buffer) => buffer.length > 0),
|
|
67
|
-
// only request events from relays once
|
|
68
|
-
distinctRelaysBatch((p) => p.id, options.refreshTimeout ?? 60_000),
|
|
69
|
-
// ensure there is only one of each event pointer
|
|
70
|
-
map(consolidateEventPointers),
|
|
71
|
-
// run the loader sequence
|
|
72
|
-
generatorSequence((pointers) => cacheFirstSequence(rxNostr, pointers, options, this.log),
|
|
73
|
-
// there will always be more events, never complete
|
|
74
|
-
false)));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { EventPacket, RxNostr } from "rx-nostr";
|
|
2
|
-
import { logger } from "applesauce-core";
|
|
3
|
-
import { CacheRequest, Loader } from "./loader.js";
|
|
4
|
-
export type LoadableSetPointer = {
|
|
5
|
-
/** A replaceable kind >= 30000 & < 40000 */
|
|
6
|
-
kind: number;
|
|
7
|
-
pubkey: string;
|
|
8
|
-
/** Relays to load from */
|
|
9
|
-
relays?: string[];
|
|
10
|
-
/** Load the sets even if it has already been loaded */
|
|
11
|
-
force?: boolean;
|
|
12
|
-
};
|
|
13
|
-
export type UserSetsLoaderOptions = {
|
|
14
|
-
/**
|
|
15
|
-
* Time interval to buffer requests in ms
|
|
16
|
-
* @default 1000
|
|
17
|
-
*/
|
|
18
|
-
bufferTime?: number;
|
|
19
|
-
/** A method used to load events from a local cache */
|
|
20
|
-
cacheRequest?: CacheRequest;
|
|
21
|
-
/**
|
|
22
|
-
* How long the loader should wait before it allows an event pointer to be refreshed from a relay
|
|
23
|
-
* @default 120000
|
|
24
|
-
*/
|
|
25
|
-
refreshTimeout?: number;
|
|
26
|
-
};
|
|
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, EventPacket> {
|
|
29
|
-
log: typeof logger;
|
|
30
|
-
constructor(rxNostr: RxNostr, opts?: UserSetsLoaderOptions);
|
|
31
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { tap, from, filter, map, mergeAll, bufferTime } from "rxjs";
|
|
2
|
-
import { createRxOneshotReq } from "rx-nostr";
|
|
3
|
-
import { markFromCache } from "applesauce-core/helpers";
|
|
4
|
-
import { logger } from "applesauce-core";
|
|
5
|
-
import { nanoid } from "nanoid";
|
|
6
|
-
import { Loader } from "./loader.js";
|
|
7
|
-
import { generatorSequence } from "../operators/generator-sequence.js";
|
|
8
|
-
import { consolidateAddressPointers, createFiltersFromAddressPointers } from "../helpers/address-pointer.js";
|
|
9
|
-
import { groupByRelay } from "../helpers/pointer.js";
|
|
10
|
-
import { distinctRelaysBatch } from "../operators/distinct-relays.js";
|
|
11
|
-
/** A generator that tries to load the address pointers from the cache first, then tries the relays */
|
|
12
|
-
function* cacheFirstSequence(rxNostr, pointers, log, opts) {
|
|
13
|
-
const id = nanoid(8);
|
|
14
|
-
log = log.extend(id);
|
|
15
|
-
// first attempt, load from cache relays
|
|
16
|
-
if (opts?.cacheRequest) {
|
|
17
|
-
log(`Checking cache`);
|
|
18
|
-
const filters = createFiltersFromAddressPointers(pointers);
|
|
19
|
-
const results = yield opts.cacheRequest(filters).pipe(
|
|
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" })));
|
|
24
|
-
if (results.length > 0) {
|
|
25
|
-
log(`Loaded ${results.length} events from cache`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
let byRelay = groupByRelay(pointers, "default");
|
|
29
|
-
// load sets from relays
|
|
30
|
-
yield from(Array.from(byRelay.entries()).map(([relay, pointers]) => {
|
|
31
|
-
let filters = createFiltersFromAddressPointers(pointers);
|
|
32
|
-
let count = 0;
|
|
33
|
-
const req = createRxOneshotReq({ filters, rxReqId: id });
|
|
34
|
-
log(`Requesting from ${relay}`, pointers);
|
|
35
|
-
let sub$;
|
|
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({
|
|
42
|
-
next: () => count++,
|
|
43
|
-
complete: () => log(`Completed ${relay}, loaded ${count} events`),
|
|
44
|
-
}));
|
|
45
|
-
})).pipe(mergeAll());
|
|
46
|
-
}
|
|
47
|
-
/** A loader that can be used to load users NIP-51 sets events ( kind >= 30000 < 40000) */
|
|
48
|
-
export class UserSetsLoader extends Loader {
|
|
49
|
-
log = logger.extend("UserSetsLoader");
|
|
50
|
-
constructor(rxNostr, opts) {
|
|
51
|
-
let options = opts || {};
|
|
52
|
-
super((source) => source.pipe(
|
|
53
|
-
// load first from cache
|
|
54
|
-
bufferTime(options?.bufferTime ?? 1000),
|
|
55
|
-
// ignore empty buffers
|
|
56
|
-
filter((buffer) => buffer.length > 0),
|
|
57
|
-
// only load from each relay once
|
|
58
|
-
distinctRelaysBatch((p) => p.kind + ":" + p.pubkey, options.refreshTimeout ?? 120_000),
|
|
59
|
-
// deduplicate address pointers
|
|
60
|
-
map(consolidateAddressPointers),
|
|
61
|
-
// check cache, relays, lookup relays in that order
|
|
62
|
-
generatorSequence((pointers) => cacheFirstSequence(rxNostr, pointers, this.log, options),
|
|
63
|
-
// there will always be more events, never complete
|
|
64
|
-
false)));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { Subject } from "rxjs";
|
|
3
|
-
import { distinctRelays } from "../distinct-relays.js";
|
|
4
|
-
describe("distinctRelays", () => {
|
|
5
|
-
it("should filter out messages with same relay within timeout window", () => {
|
|
6
|
-
const fn = vi.fn();
|
|
7
|
-
const source$ = new Subject();
|
|
8
|
-
source$.pipe(distinctRelays((msg) => msg.id, 1000)).subscribe(fn);
|
|
9
|
-
const message = {
|
|
10
|
-
id: "123",
|
|
11
|
-
relays: ["relay1", "relay2"],
|
|
12
|
-
};
|
|
13
|
-
// Send message with two relays
|
|
14
|
-
source$.next(message);
|
|
15
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
16
|
-
expect(fn).toHaveBeenCalledWith(message);
|
|
17
|
-
// send message again
|
|
18
|
-
source$.next({ ...message });
|
|
19
|
-
// should not call again
|
|
20
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
21
|
-
});
|
|
22
|
-
it("should only remove duplicate relays in timeout window", () => {
|
|
23
|
-
const fn = vi.fn();
|
|
24
|
-
const source$ = new Subject();
|
|
25
|
-
source$.pipe(distinctRelays((msg) => msg.id, 1000)).subscribe(fn);
|
|
26
|
-
const message = {
|
|
27
|
-
id: "123",
|
|
28
|
-
relays: ["relay1", "relay2"],
|
|
29
|
-
};
|
|
30
|
-
// Send message with two relays
|
|
31
|
-
source$.next(message);
|
|
32
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
33
|
-
expect(fn).toHaveBeenCalledWith(message);
|
|
34
|
-
// send message again
|
|
35
|
-
source$.next({ id: "123", relays: ["relay1", "relay3"] });
|
|
36
|
-
// should not call again
|
|
37
|
-
expect(fn).toHaveBeenCalledTimes(2);
|
|
38
|
-
expect(fn).toHaveBeenCalledWith({ id: "123", relays: ["relay3"] });
|
|
39
|
-
});
|
|
40
|
-
it("should filter out duplicate messages without relays in timeout", () => {
|
|
41
|
-
const fn = vi.fn();
|
|
42
|
-
const source$ = new Subject();
|
|
43
|
-
source$.pipe(distinctRelays((msg) => msg.id, 1000)).subscribe(fn);
|
|
44
|
-
const message = { id: "123" };
|
|
45
|
-
// Send message with two relays
|
|
46
|
-
source$.next(message);
|
|
47
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
48
|
-
expect(fn).toHaveBeenCalledWith(message);
|
|
49
|
-
// send message again
|
|
50
|
-
source$.next({ ...message });
|
|
51
|
-
// should not call again
|
|
52
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
53
|
-
});
|
|
54
|
-
it("should treat messages with relays severalty then messages without", () => {
|
|
55
|
-
const fn = vi.fn();
|
|
56
|
-
const source$ = new Subject();
|
|
57
|
-
source$.pipe(distinctRelays((msg) => msg.id, 1000)).subscribe(fn);
|
|
58
|
-
const withRelays = {
|
|
59
|
-
id: "123",
|
|
60
|
-
relays: ["relay1", "relay2"],
|
|
61
|
-
};
|
|
62
|
-
const withoutRelays = {
|
|
63
|
-
id: "123",
|
|
64
|
-
};
|
|
65
|
-
// Send message with two relays
|
|
66
|
-
source$.next(withoutRelays);
|
|
67
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
68
|
-
expect(fn).toHaveBeenCalledWith(withoutRelays);
|
|
69
|
-
// send message with relays
|
|
70
|
-
source$.next(withRelays);
|
|
71
|
-
// should not call again
|
|
72
|
-
expect(fn).toHaveBeenCalledTimes(2);
|
|
73
|
-
expect(fn).toHaveBeenCalledWith(withRelays);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|