applesauce-loaders 2.0.0 → 3.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/dist/helpers/address-pointer.d.ts +0 -14
- package/dist/helpers/address-pointer.js +3 -49
- package/dist/helpers/cache.d.ts +1 -3
- package/dist/helpers/cache.js +0 -4
- package/dist/helpers/loaders.d.ts +6 -1
- package/dist/helpers/loaders.js +26 -11
- package/dist/loaders/address-loader.d.ts +14 -3
- package/dist/loaders/address-loader.js +54 -27
- package/dist/loaders/event-loader.d.ts +5 -2
- package/dist/loaders/event-loader.js +17 -19
- package/dist/loaders/tag-value-loader.d.ts +1 -1
- package/dist/loaders/tag-value-loader.js +17 -19
- package/dist/loaders/user-lists-loader.d.ts +1 -1
- package/dist/loaders/user-lists-loader.js +6 -6
- package/package.json +3 -3
|
@@ -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
|
-
/** Ignore all forms of caching */
|
|
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
|
-
/** @deprecated use mergeRelaySets instead */
|
|
27
|
-
export declare function getRelaysFromPointers(pointers: AddressPointerWithoutD[]): Set<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 { createReplaceableAddress, 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,48 +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
|
-
/** @deprecated use mergeRelaySets instead */
|
|
66
|
-
export function getRelaysFromPointers(pointers) {
|
|
67
|
-
const relays = new Set();
|
|
68
|
-
for (const pointer of pointers) {
|
|
69
|
-
if (!pointer.relays)
|
|
70
|
-
continue;
|
|
71
|
-
for (const relay of pointer.relays) {
|
|
72
|
-
relays.add(relay);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return relays;
|
|
76
|
-
}
|
|
77
|
-
/** deep clone a loadable pointer to ensure its safe to modify */
|
|
78
|
-
function cloneLoadablePointer(pointer) {
|
|
79
|
-
const clone = { ...pointer };
|
|
80
|
-
if (pointer.relays)
|
|
81
|
-
clone.relays = [...pointer.relays];
|
|
82
|
-
return clone;
|
|
83
|
-
}
|
|
84
|
-
/** deduplicates an array of address pointers and merges their relays array */
|
|
85
|
-
export function consolidateAddressPointers(pointers) {
|
|
86
|
-
const byAddress = new Map();
|
|
87
|
-
for (const pointer of pointers) {
|
|
88
|
-
const addr = createReplaceableAddress(pointer.kind, pointer.pubkey, pointer.identifier);
|
|
89
|
-
if (byAddress.has(addr)) {
|
|
90
|
-
// duplicate, merge pointers
|
|
91
|
-
const current = byAddress.get(addr);
|
|
92
|
-
// merge relays
|
|
93
|
-
if (pointer.relays) {
|
|
94
|
-
if (current.relays)
|
|
95
|
-
current.relays = mergeRelaySets(current.relays, pointer.relays);
|
|
96
|
-
else
|
|
97
|
-
current.relays = pointer.relays;
|
|
98
|
-
}
|
|
99
|
-
// merge force flag
|
|
100
|
-
if (pointer.force !== undefined) {
|
|
101
|
-
current.force = current.force || pointer.force;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
else
|
|
105
|
-
byAddress.set(addr, cloneLoadablePointer(pointer));
|
|
106
|
-
}
|
|
107
|
-
// return consolidated pointers
|
|
108
|
-
return Array.from(byAddress.values());
|
|
109
|
-
}
|
package/dist/helpers/cache.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Observable } from "rxjs";
|
|
2
2
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
3
|
-
import { CacheRequest
|
|
3
|
+
import { CacheRequest } from "../types.js";
|
|
4
4
|
/** Calls the cache request and converts the reponse into an observable */
|
|
5
5
|
export declare function unwrapCacheRequest(request: CacheRequest, filters: Filter[]): Observable<NostrEvent>;
|
|
6
6
|
/** Calls a cache request method with filters and marks all returned events as being from the cache */
|
|
7
7
|
export declare function makeCacheRequest(request: CacheRequest, filters: Filter[]): Observable<NostrEvent>;
|
|
8
|
-
/** Wraps a cache request method and returns a FilterRequest */
|
|
9
|
-
export declare function wrapCacheRequest(request: CacheRequest): FilterRequest;
|
package/dist/helpers/cache.js
CHANGED
|
@@ -16,7 +16,3 @@ export function unwrapCacheRequest(request, filters) {
|
|
|
16
16
|
export function makeCacheRequest(request, filters) {
|
|
17
17
|
return unwrapCacheRequest(request, filters).pipe(tap((e) => markFromCache(e)));
|
|
18
18
|
}
|
|
19
|
-
/** Wraps a cache request method and returns a FilterRequest */
|
|
20
|
-
export function wrapCacheRequest(request) {
|
|
21
|
-
return (filters) => makeCacheRequest(request, filters);
|
|
22
|
-
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { MonoTypeOperatorFunction, Observable, OperatorFunction } from "rxjs";
|
|
2
|
-
/**
|
|
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
|
+
*/
|
|
3
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>;
|
package/dist/helpers/loaders.js
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
|
-
import { filter, identity,
|
|
2
|
-
/**
|
|
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
|
+
*/
|
|
3
10
|
export function batchLoader(buffer, upstream, matcher, output) {
|
|
4
11
|
const queue = new Subject();
|
|
5
|
-
const
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
+
});
|
|
12
22
|
return (input) => new Observable((observer) => {
|
|
13
23
|
// Add the pointer to the queue when observable is subscribed
|
|
14
|
-
setTimeout
|
|
15
|
-
|
|
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
|
|
16
31
|
.pipe(
|
|
17
32
|
// wait for the next batch to run
|
|
18
33
|
take(1),
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { IEventStore } from "applesauce-core";
|
|
2
2
|
import { NostrEvent } from "nostr-tools";
|
|
3
3
|
import { Observable } from "rxjs";
|
|
4
|
-
import { LoadableAddressPointer } from "../helpers/address-pointer.js";
|
|
5
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
|
+
};
|
|
6
15
|
/** A method that takes address pointers and returns an observable of events */
|
|
7
16
|
export type AddressPointersLoader = (pointers: LoadableAddressPointer[]) => Observable<NostrEvent>;
|
|
8
17
|
export type AddressPointerLoader = (pointer: LoadableAddressPointer) => Observable<NostrEvent>;
|
|
@@ -17,6 +26,8 @@ export declare function relayHintsAddressPointersLoader(request: NostrRequest):
|
|
|
17
26
|
export declare function relaysAddressPointersLoader(request: NostrRequest, relays: Observable<string[]> | string[]): AddressPointersLoader;
|
|
18
27
|
/** Creates a loader that loads all event pointers based on their relays */
|
|
19
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[];
|
|
20
31
|
export type AddressLoaderOptions = Partial<{
|
|
21
32
|
/** Time interval to buffer requests in ms ( default 1000 ) */
|
|
22
33
|
bufferTime: number;
|
|
@@ -28,10 +39,10 @@ export type AddressLoaderOptions = Partial<{
|
|
|
28
39
|
cacheRequest: CacheRequest;
|
|
29
40
|
/** Whether to follow relay hints ( default true ) */
|
|
30
41
|
followRelayHints: boolean;
|
|
31
|
-
/** Fallback lookup relays to check when event cant be found */
|
|
32
|
-
lookupRelays: string[] | Observable<string[]>;
|
|
33
42
|
/** An array of relays to always fetch from */
|
|
34
43
|
extraRelays: string[] | Observable<string[]>;
|
|
44
|
+
/** Fallback lookup relays to check when event cant be found */
|
|
45
|
+
lookupRelays: string[] | Observable<string[]>;
|
|
35
46
|
}>;
|
|
36
47
|
/** Create a pre-built address pointer loader that supports batching, caching, and lookup relays */
|
|
37
48
|
export declare function createAddressLoader(pool: UpstreamPool, opts?: AddressLoaderOptions): AddressPointerLoader;
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { mapEventsToStore } from "applesauce-core";
|
|
2
2
|
import { createReplaceableAddress, getReplaceableAddress, getReplaceableIdentifier, isReplaceable, mergeRelaySets, } from "applesauce-core/helpers";
|
|
3
|
-
import { bufferTime, catchError, EMPTY
|
|
4
|
-
import {
|
|
5
|
-
import { makeCacheRequest
|
|
6
|
-
import { batchLoader } from "../helpers/loaders.js";
|
|
7
|
-
import { wrapGeneratorFunction } from "../operators/generator.js";
|
|
3
|
+
import { bufferTime, catchError, EMPTY } from "rxjs";
|
|
4
|
+
import { createFiltersFromAddressPointers, isLoadableAddressPointer } from "../helpers/address-pointer.js";
|
|
5
|
+
import { makeCacheRequest } from "../helpers/cache.js";
|
|
6
|
+
import { batchLoader, unwrap } from "../helpers/loaders.js";
|
|
8
7
|
import { wrapUpstreamPool } from "../helpers/upstream.js";
|
|
8
|
+
import { wrapGeneratorFunction } from "../operators/generator.js";
|
|
9
9
|
/**
|
|
10
10
|
* Loads address pointers from an async cache
|
|
11
11
|
* @note ignores pointers with force=true
|
|
12
12
|
*/
|
|
13
13
|
export function cacheAddressPointersLoader(request) {
|
|
14
|
-
return (pointers) =>
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
return (pointers) => {
|
|
15
|
+
pointers = pointers.filter((p) => p.cache !== false);
|
|
16
|
+
// Skip if there are no pointers to load from cache
|
|
17
|
+
if (pointers.length === 0)
|
|
18
|
+
return EMPTY;
|
|
19
|
+
return makeCacheRequest(request, createFiltersFromAddressPointers(pointers));
|
|
20
|
+
};
|
|
17
21
|
}
|
|
18
22
|
/** Loads address pointers from the relay hints */
|
|
19
23
|
export function relayHintsAddressPointersLoader(request) {
|
|
@@ -27,22 +31,22 @@ export function relayHintsAddressPointersLoader(request) {
|
|
|
27
31
|
}
|
|
28
32
|
/** Loads address pointers from an array of relays */
|
|
29
33
|
export function relaysAddressPointersLoader(request, relays) {
|
|
30
|
-
return (pointers) =>
|
|
31
|
-
// Resolve the relays as an observable
|
|
32
|
-
(isObservable(relays) ? relays : of(relays)).pipe(
|
|
33
|
-
// Only take the first value
|
|
34
|
-
take(1),
|
|
35
|
-
// Make the request
|
|
36
|
-
switchMap((relays) => {
|
|
34
|
+
return (pointers) => unwrap(relays, (relays) => {
|
|
37
35
|
if (relays.length === 0)
|
|
38
36
|
return EMPTY;
|
|
39
37
|
const filters = createFiltersFromAddressPointers(pointers);
|
|
40
38
|
return request(relays, filters);
|
|
41
|
-
})
|
|
39
|
+
});
|
|
42
40
|
}
|
|
43
41
|
/** Creates a loader that loads all event pointers based on their relays */
|
|
44
42
|
export function addressPointerLoadingSequence(...loaders) {
|
|
45
43
|
return wrapGeneratorFunction(function* (pointers) {
|
|
44
|
+
// Filter out invalid pointers and consolidate
|
|
45
|
+
pointers = consolidateAddressPointers(pointers.filter(isLoadableAddressPointer));
|
|
46
|
+
// Skip if there are no pointers
|
|
47
|
+
if (pointers.length === 0)
|
|
48
|
+
return;
|
|
49
|
+
// Keep track of remaining pointers to load
|
|
46
50
|
let remaining = Array.from(pointers);
|
|
47
51
|
for (const loader of loaders) {
|
|
48
52
|
if (loader === undefined)
|
|
@@ -53,32 +57,55 @@ export function addressPointerLoadingSequence(...loaders) {
|
|
|
53
57
|
// Get set of addresses loaded
|
|
54
58
|
const addresses = new Set(results.filter((e) => isReplaceable(e.kind)).map((event) => getReplaceableAddress(event)));
|
|
55
59
|
// Remove the pointers that were loaded
|
|
56
|
-
remaining = remaining.filter((p) => !addresses.has(createReplaceableAddress(p.kind, p.pubkey, p.identifier))
|
|
60
|
+
remaining = remaining.filter((p) => !addresses.has(createReplaceableAddress(p.kind, p.pubkey, p.identifier)));
|
|
57
61
|
// If there are no remaining pointers, complete
|
|
58
62
|
if (remaining.length === 0)
|
|
59
63
|
return;
|
|
60
64
|
}
|
|
61
65
|
});
|
|
62
66
|
}
|
|
67
|
+
/** deep clone a loadable pointer to ensure its safe to modify */
|
|
68
|
+
function cloneLoadablePointer(pointer) {
|
|
69
|
+
const clone = { ...pointer };
|
|
70
|
+
if (pointer.relays)
|
|
71
|
+
clone.relays = [...pointer.relays];
|
|
72
|
+
return clone;
|
|
73
|
+
}
|
|
74
|
+
/** deduplicates an array of address pointers and merges their relays array */
|
|
75
|
+
export function consolidateAddressPointers(pointers) {
|
|
76
|
+
const byAddress = new Map();
|
|
77
|
+
for (const pointer of pointers) {
|
|
78
|
+
const addr = createReplaceableAddress(pointer.kind, pointer.pubkey, pointer.identifier);
|
|
79
|
+
if (byAddress.has(addr)) {
|
|
80
|
+
// duplicate, merge pointers
|
|
81
|
+
const current = byAddress.get(addr);
|
|
82
|
+
// merge relays
|
|
83
|
+
if (pointer.relays && pointer.relays.length > 0) {
|
|
84
|
+
if (current.relays)
|
|
85
|
+
current.relays = mergeRelaySets(current.relays, pointer.relays);
|
|
86
|
+
else
|
|
87
|
+
current.relays = pointer.relays;
|
|
88
|
+
}
|
|
89
|
+
// merge cache flag
|
|
90
|
+
if (pointer.cache === false)
|
|
91
|
+
current.cache = false;
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
byAddress.set(addr, cloneLoadablePointer(pointer));
|
|
95
|
+
}
|
|
96
|
+
// return consolidated pointers
|
|
97
|
+
return Array.from(byAddress.values());
|
|
98
|
+
}
|
|
63
99
|
/** Create a pre-built address pointer loader that supports batching, caching, and lookup relays */
|
|
64
100
|
export function createAddressLoader(pool, opts) {
|
|
65
101
|
const request = wrapUpstreamPool(pool);
|
|
66
|
-
const cacheRequest = opts?.cacheRequest ? wrapCacheRequest(opts.cacheRequest) : undefined;
|
|
67
102
|
return batchLoader(
|
|
68
|
-
// Create batching sequence
|
|
69
|
-
pipe(
|
|
70
|
-
// filter out invalid pointers
|
|
71
|
-
filter(isLoadableAddressPointer),
|
|
72
103
|
// buffer requests by time or size
|
|
73
104
|
bufferTime(opts?.bufferTime ?? 1000, undefined, opts?.bufferSize ?? 200),
|
|
74
|
-
// Ingore empty buffers
|
|
75
|
-
filter((b) => b.length > 0),
|
|
76
|
-
// consolidate buffered pointers
|
|
77
|
-
map(consolidateAddressPointers)),
|
|
78
105
|
// Create a loader for batching
|
|
79
106
|
addressPointerLoadingSequence(
|
|
80
107
|
// Step 1. load from cache if available
|
|
81
|
-
cacheRequest ? cacheAddressPointersLoader(cacheRequest) : undefined,
|
|
108
|
+
opts?.cacheRequest ? cacheAddressPointersLoader(opts.cacheRequest) : undefined,
|
|
82
109
|
// Step 2. load from relay hints on pointers
|
|
83
110
|
opts?.followRelayHints !== false ? relayHintsAddressPointersLoader(request) : undefined,
|
|
84
111
|
// Step 3. load from extra relays
|
|
@@ -3,8 +3,11 @@ import { NostrEvent } from "nostr-tools";
|
|
|
3
3
|
import { EventPointer } from "nostr-tools/nip19";
|
|
4
4
|
import { Observable } from "rxjs";
|
|
5
5
|
import { CacheRequest, NostrRequest, UpstreamPool } from "../types.js";
|
|
6
|
-
export type
|
|
7
|
-
|
|
6
|
+
export type LoadableEventPointer = EventPointer & {
|
|
7
|
+
cache?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type EventPointerLoader = (pointer: LoadableEventPointer) => Observable<NostrEvent>;
|
|
10
|
+
export type createEventLoader = (pointers: LoadableEventPointer[]) => Observable<NostrEvent>;
|
|
8
11
|
/** Creates a loader that gets a single event from the cache */
|
|
9
12
|
export declare function cacheEventsLoader(request: CacheRequest): createEventLoader;
|
|
10
13
|
/** Creates a loader that gets an array of events from a list of relays */
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import { mapEventsToStore } from "applesauce-core";
|
|
2
|
-
import { bufferTime, catchError, EMPTY,
|
|
3
|
-
import { makeCacheRequest
|
|
2
|
+
import { bufferTime, catchError, EMPTY, merge, tap } from "rxjs";
|
|
3
|
+
import { makeCacheRequest } from "../helpers/cache.js";
|
|
4
4
|
import { consolidateEventPointers } from "../helpers/event-pointer.js";
|
|
5
|
-
import { batchLoader } from "../helpers/loaders.js";
|
|
5
|
+
import { batchLoader, unwrap } from "../helpers/loaders.js";
|
|
6
6
|
import { groupByRelay } from "../helpers/pointer.js";
|
|
7
|
-
import { wrapGeneratorFunction } from "../operators/generator.js";
|
|
8
7
|
import { wrapUpstreamPool } from "../helpers/upstream.js";
|
|
8
|
+
import { wrapGeneratorFunction } from "../operators/generator.js";
|
|
9
9
|
/** Creates a loader that gets a single event from the cache */
|
|
10
10
|
export function cacheEventsLoader(request) {
|
|
11
|
-
return (pointers) =>
|
|
11
|
+
return (pointers) => {
|
|
12
|
+
pointers = pointers.filter((p) => p.cache !== false);
|
|
13
|
+
if (pointers.length === 0)
|
|
14
|
+
return EMPTY;
|
|
15
|
+
return makeCacheRequest(request, [{ ids: pointers.map((p) => p.id) }]);
|
|
16
|
+
};
|
|
12
17
|
}
|
|
13
18
|
/** Creates a loader that gets an array of events from a list of relays */
|
|
14
19
|
export function relaysEventsLoader(request, relays) {
|
|
15
|
-
return (pointers) =>
|
|
16
|
-
// unwrap relays observable
|
|
17
|
-
(isObservable(relays) ? relays : of(relays)).pipe(
|
|
18
|
-
// take the first value
|
|
19
|
-
take(1),
|
|
20
|
-
// Request events from relays
|
|
21
|
-
switchMap((relays) => request(relays, [{ ids: pointers.map((p) => p.id) }])));
|
|
20
|
+
return (pointers) => unwrap(relays, (relays) => request(relays, [{ ids: pointers.map((p) => p.id) }]));
|
|
22
21
|
}
|
|
23
22
|
/** Creates a loader that gets an array of events from a single relay */
|
|
24
23
|
export function relayEventsLoader(request, relay) {
|
|
@@ -46,6 +45,11 @@ export function relayHintsEventsLoader(request, upstream = relayEventsLoader) {
|
|
|
46
45
|
/** Creates a loader that tries to load events from a list of loaders in order */
|
|
47
46
|
export function eventLoadingSequence(...loaders) {
|
|
48
47
|
return wrapGeneratorFunction(function* (pointers) {
|
|
48
|
+
// Filter out invalid pointers and consolidate
|
|
49
|
+
pointers = consolidateEventPointers(pointers);
|
|
50
|
+
// Skip if there are no pointers
|
|
51
|
+
if (pointers.length === 0)
|
|
52
|
+
return;
|
|
49
53
|
const found = new Set();
|
|
50
54
|
let remaining = Array.from(pointers);
|
|
51
55
|
for (const loader of loaders) {
|
|
@@ -67,20 +71,14 @@ export function eventLoadingSequence(...loaders) {
|
|
|
67
71
|
/** Create a pre-built address pointer loader that supports batching, caching, and lookup relays */
|
|
68
72
|
export function createEventLoader(pool, opts) {
|
|
69
73
|
const request = wrapUpstreamPool(pool);
|
|
70
|
-
const cacheRequest = opts?.cacheRequest ? wrapCacheRequest(opts.cacheRequest) : undefined;
|
|
71
74
|
return batchLoader(
|
|
72
75
|
// Create batching sequence
|
|
73
|
-
pipe(
|
|
74
76
|
// buffer requests by time or size
|
|
75
77
|
bufferTime(opts?.bufferTime ?? 1000, undefined, opts?.bufferSize ?? 200),
|
|
76
|
-
// Ingore empty buffers
|
|
77
|
-
filter((b) => b.length > 0),
|
|
78
|
-
// consolidate buffered pointers
|
|
79
|
-
map(consolidateEventPointers)),
|
|
80
78
|
// Create a loader for batching
|
|
81
79
|
eventLoadingSequence(
|
|
82
80
|
// Step 1. load from cache if available
|
|
83
|
-
cacheRequest ? cacheEventsLoader(cacheRequest) : undefined,
|
|
81
|
+
opts?.cacheRequest ? cacheEventsLoader(opts.cacheRequest) : undefined,
|
|
84
82
|
// Step 2. load from relay hints on pointers
|
|
85
83
|
opts?.followRelayHints !== false ? relayHintsEventsLoader(request) : undefined,
|
|
86
84
|
// Step 3. load from extra relays
|
|
@@ -24,7 +24,7 @@ export type TagValueLoaderOptions = {
|
|
|
24
24
|
/** Method used to load from the cache */
|
|
25
25
|
cacheRequest?: CacheRequest;
|
|
26
26
|
/** An array of relays to always fetch from */
|
|
27
|
-
extraRelays?: string[]
|
|
27
|
+
extraRelays?: string[] | Observable<string[]>;
|
|
28
28
|
/** An event store used to deduplicate events */
|
|
29
29
|
eventStore?: IEventStore;
|
|
30
30
|
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { mapEventsToStore } from "applesauce-core";
|
|
2
2
|
import { mergeRelaySets } from "applesauce-core/helpers";
|
|
3
|
-
import { bufferTime,
|
|
3
|
+
import { bufferTime, EMPTY, merge } from "rxjs";
|
|
4
4
|
import { unique } from "../helpers/array.js";
|
|
5
|
-
import { makeCacheRequest
|
|
6
|
-
import { batchLoader } from "../helpers/loaders.js";
|
|
7
|
-
import { distinctRelaysBatch } from "../operators/distinct-relays.js";
|
|
5
|
+
import { makeCacheRequest } from "../helpers/cache.js";
|
|
6
|
+
import { batchLoader, unwrap } from "../helpers/loaders.js";
|
|
8
7
|
import { wrapUpstreamPool } from "../helpers/upstream.js";
|
|
9
8
|
/** Creates a loader that gets tag values from the cache */
|
|
10
9
|
export function cacheTagValueLoader(request, tagName, opts) {
|
|
@@ -24,7 +23,7 @@ export function cacheTagValueLoader(request, tagName, opts) {
|
|
|
24
23
|
/** Creates a loader that gets tag values from relays */
|
|
25
24
|
export function relaysTagValueLoader(request, tagName, opts) {
|
|
26
25
|
const filterTag = `#${tagName}`;
|
|
27
|
-
return (pointers) => {
|
|
26
|
+
return (pointers) => unwrap(opts?.extraRelays, (extraRelays) => {
|
|
28
27
|
const baseFilter = {};
|
|
29
28
|
if (opts?.kinds)
|
|
30
29
|
baseFilter.kinds = opts.kinds;
|
|
@@ -34,7 +33,7 @@ export function relaysTagValueLoader(request, tagName, opts) {
|
|
|
34
33
|
baseFilter.since = opts.since;
|
|
35
34
|
// build request map for relays
|
|
36
35
|
const requestMap = pointers.reduce((map, pointer) => {
|
|
37
|
-
const relays = mergeRelaySets(pointer.relays,
|
|
36
|
+
const relays = mergeRelaySets(pointer.relays, extraRelays);
|
|
38
37
|
for (const relay of relays) {
|
|
39
38
|
if (!map[relay]) {
|
|
40
39
|
// create new filter for relay
|
|
@@ -49,27 +48,26 @@ export function relaysTagValueLoader(request, tagName, opts) {
|
|
|
49
48
|
}, {});
|
|
50
49
|
const requests = Object.entries(requestMap).map(([relay, filter]) => request([relay], [filter]));
|
|
51
50
|
return merge(...requests);
|
|
52
|
-
};
|
|
51
|
+
});
|
|
53
52
|
}
|
|
54
53
|
/** Create a pre-built tag value loader that supports batching, caching, and relay hints */
|
|
55
54
|
export function createTagValueLoader(pool, tagName, opts) {
|
|
56
55
|
const request = wrapUpstreamPool(pool);
|
|
57
|
-
const cacheRequest = opts?.cacheRequest ? wrapCacheRequest(opts.cacheRequest) : undefined;
|
|
58
56
|
return batchLoader(
|
|
59
|
-
// Create batching sequence
|
|
60
|
-
pipe(
|
|
61
57
|
// buffer requests by time or size
|
|
62
58
|
bufferTime(opts?.bufferTime ?? 1000, undefined, opts?.bufferSize ?? 200),
|
|
63
|
-
// Ignore empty buffers
|
|
64
|
-
filter((b) => b.length > 0),
|
|
65
|
-
// Only request from each relay once
|
|
66
|
-
distinctRelaysBatch((m) => m.value)),
|
|
67
59
|
// Create a loader for batching
|
|
68
|
-
(pointers) =>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
(pointers) => {
|
|
61
|
+
// Skip if there are no pointers
|
|
62
|
+
if (pointers.length === 0)
|
|
63
|
+
return EMPTY;
|
|
64
|
+
// Load from cache and relays in parallel
|
|
65
|
+
return merge(
|
|
66
|
+
// load from cache if available
|
|
67
|
+
opts?.cacheRequest ? cacheTagValueLoader(opts.cacheRequest, tagName, opts)(pointers) : [],
|
|
68
|
+
// load from relays
|
|
69
|
+
relaysTagValueLoader(request, tagName, opts)(pointers));
|
|
70
|
+
},
|
|
73
71
|
// Filter results based on requests
|
|
74
72
|
(pointer, event) => event.tags.some((tag) => tag[0] === tagName && tag[1] === pointer.value),
|
|
75
73
|
// Pass all events through the store if defined
|
|
@@ -15,7 +15,7 @@ export type UserListsLoaderOptions = Partial<{
|
|
|
15
15
|
/** A method used to load events from a local cache */
|
|
16
16
|
cacheRequest?: CacheRequest;
|
|
17
17
|
/** An array of extra relay to load from */
|
|
18
|
-
extraRelays?: string[]
|
|
18
|
+
extraRelays?: string[] | Observable<string[]>;
|
|
19
19
|
/** An event store used to deduplicate events */
|
|
20
20
|
eventStore: IEventStore;
|
|
21
21
|
}>;
|
|
@@ -2,7 +2,8 @@ import { mapEventsToStore } from "applesauce-core";
|
|
|
2
2
|
import { mergeRelaySets } from "applesauce-core/helpers";
|
|
3
3
|
import { kinds } from "nostr-tools";
|
|
4
4
|
import { EMPTY, identity, merge } from "rxjs";
|
|
5
|
-
import { makeCacheRequest
|
|
5
|
+
import { makeCacheRequest } from "../helpers/cache.js";
|
|
6
|
+
import { unwrap } from "../helpers/loaders.js";
|
|
6
7
|
import { wrapUpstreamPool } from "../helpers/upstream.js";
|
|
7
8
|
/** A list of NIP-51 list kinds that most clients will use */
|
|
8
9
|
export const COMMON_LIST_KINDS = [kinds.Contacts, kinds.Mutelist, kinds.Pinlist, kinds.BookmarkList];
|
|
@@ -14,20 +15,19 @@ export const COMMON_SET_KINDS = [kinds.Bookmarksets, kinds.Followsets];
|
|
|
14
15
|
*/
|
|
15
16
|
export function createUserListsLoader(pool, opts) {
|
|
16
17
|
const request = wrapUpstreamPool(pool);
|
|
17
|
-
|
|
18
|
-
return (user) => {
|
|
18
|
+
return (user) => unwrap(opts?.extraRelays, (extraRelays) => {
|
|
19
19
|
const filter = {
|
|
20
20
|
kinds: opts?.kinds || [...COMMON_LIST_KINDS, ...COMMON_SET_KINDS],
|
|
21
21
|
authors: [user.pubkey],
|
|
22
22
|
};
|
|
23
23
|
// Merge extra relays with user relays
|
|
24
|
-
const relays =
|
|
24
|
+
const relays = mergeRelaySets(user.relays, extraRelays);
|
|
25
25
|
return merge(
|
|
26
26
|
// Load from cache
|
|
27
|
-
cacheRequest ? makeCacheRequest(cacheRequest, [filter]) : EMPTY,
|
|
27
|
+
opts?.cacheRequest ? makeCacheRequest(opts.cacheRequest, [filter]) : EMPTY,
|
|
28
28
|
// Load from relays
|
|
29
29
|
relays ? request(relays, [filter]) : EMPTY).pipe(
|
|
30
30
|
// If event store is set, deduplicate events
|
|
31
31
|
opts?.eventStore ? mapEventsToStore(opts.eventStore) : identity);
|
|
32
|
-
};
|
|
32
|
+
});
|
|
33
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-loaders",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.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",
|
|
@@ -53,14 +53,14 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"applesauce-core": "^
|
|
56
|
+
"applesauce-core": "^3.0.0",
|
|
57
57
|
"nanoid": "^5.0.9",
|
|
58
58
|
"nostr-tools": "^2.13",
|
|
59
59
|
"rxjs": "^7.8.1"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@hirez_io/observer-spy": "^2.2.0",
|
|
63
|
-
"applesauce-signers": "^
|
|
63
|
+
"applesauce-signers": "^3.0.0",
|
|
64
64
|
"typescript": "^5.8.3",
|
|
65
65
|
"vitest": "^3.2.3",
|
|
66
66
|
"vitest-nostr": "^0.4.1",
|