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
|
@@ -1,56 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.loaders = loaders;
|
|
51
|
-
this.log = this.log.extend(this.id);
|
|
52
|
-
}
|
|
53
|
-
static simpleFilterMap(relays, filters) {
|
|
54
|
-
return relays.reduce((map, relay) => ({ ...map, [relay]: filters }), {});
|
|
55
|
-
}
|
|
1
|
+
import { mapEventsToStore } from "applesauce-core";
|
|
2
|
+
import { EMPTY, finalize, identity, merge, tap } from "rxjs";
|
|
3
|
+
import { makeCacheRequest } from "../helpers/cache.js";
|
|
4
|
+
import { wrapUpstreamPool } from "../helpers/upstream.js";
|
|
5
|
+
/** A loader that loads blocks of events until none are returned or the since timestamp is reached */
|
|
6
|
+
export function filterBlockLoader(request, filters, opts) {
|
|
7
|
+
let cursor = Infinity;
|
|
8
|
+
let complete = false;
|
|
9
|
+
return (since) => {
|
|
10
|
+
if (complete)
|
|
11
|
+
return EMPTY;
|
|
12
|
+
if (since !== undefined && cursor <= since)
|
|
13
|
+
return EMPTY;
|
|
14
|
+
// Keep loading blocks until none are returned or an event is found that is ealier then the new cursor
|
|
15
|
+
const withTime = filters.map((filter) => ({
|
|
16
|
+
...filter,
|
|
17
|
+
limit: filter.limit || opts?.limit,
|
|
18
|
+
until: cursor !== Infinity ? cursor : undefined,
|
|
19
|
+
}));
|
|
20
|
+
let count = 0;
|
|
21
|
+
// Load the next block of events
|
|
22
|
+
return request(withTime).pipe(tap((event) => {
|
|
23
|
+
// Update the cursor to the oldest event
|
|
24
|
+
cursor = Math.min(event.created_at - 1, cursor);
|
|
25
|
+
count++;
|
|
26
|
+
}), finalize(() => {
|
|
27
|
+
// Set the loader to complete if no events are returned
|
|
28
|
+
if (count === 0)
|
|
29
|
+
complete = true;
|
|
30
|
+
}));
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Creates a loader that loads a timeline from a cache */
|
|
34
|
+
export function cacheTimelineLoader(request, filters, opts) {
|
|
35
|
+
return filterBlockLoader((filters) => makeCacheRequest(request, filters), filters, opts);
|
|
36
|
+
}
|
|
37
|
+
/** Creates a timeline loader that loads the same filters from multiple relays */
|
|
38
|
+
export function relaysTimelineLoader(request, relays, filters, opts) {
|
|
39
|
+
const loaders = relays.map((relay) => filterBlockLoader((f) => request([relay], f), filters, opts));
|
|
40
|
+
return (since) => merge(...loaders.map((l) => l(since)));
|
|
41
|
+
}
|
|
42
|
+
/** A common timeline loader that takes an array of relays and a cache method */
|
|
43
|
+
export function createTimelineLoader(pool, relays, filters, opts) {
|
|
44
|
+
if (!Array.isArray(filters))
|
|
45
|
+
filters = [filters];
|
|
46
|
+
const request = wrapUpstreamPool(pool);
|
|
47
|
+
const cacheLoader = opts?.cache && cacheTimelineLoader(opts.cache, filters, opts);
|
|
48
|
+
const relayLoader = relaysTimelineLoader(request, relays, filters, opts);
|
|
49
|
+
return (since) => merge(cacheLoader?.(since) ?? EMPTY, relayLoader?.(since)).pipe(opts?.eventStore ? mapEventsToStore(opts.eventStore) : identity);
|
|
56
50
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IEventStore } from "applesauce-core";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
import { ProfilePointer } from "nostr-tools/nip19";
|
|
4
|
+
import { Observable } from "rxjs";
|
|
5
|
+
import { CacheRequest, UpstreamPool } from "../types.js";
|
|
6
|
+
/** A list of NIP-51 list kinds that most clients will use */
|
|
7
|
+
export declare const COMMON_LIST_KINDS: number[];
|
|
8
|
+
/** A list of NIP-51 set kinds that most clients will use */
|
|
9
|
+
export declare const COMMON_SET_KINDS: number[];
|
|
10
|
+
/** A loader that takes a user profile and loads their NIP-51 sets and lists */
|
|
11
|
+
export type UserListsLoader = (user: ProfilePointer) => Observable<NostrEvent>;
|
|
12
|
+
export type UserListsLoaderOptions = Partial<{
|
|
13
|
+
/** An array of NIP-51 kinds to load */
|
|
14
|
+
kinds: number[];
|
|
15
|
+
/** A method used to load events from a local cache */
|
|
16
|
+
cacheRequest?: CacheRequest;
|
|
17
|
+
/** An array of extra relay to load from */
|
|
18
|
+
extraRelays?: string[];
|
|
19
|
+
/** An event store used to deduplicate events */
|
|
20
|
+
eventStore: IEventStore;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* A special address loader that can request addressable events without specifying the identifier
|
|
24
|
+
* @todo this does not have a buffer, it may be useful to have one
|
|
25
|
+
*/
|
|
26
|
+
export declare function createUserListsLoader(pool: UpstreamPool, opts?: UserListsLoaderOptions): UserListsLoader;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mapEventsToStore } from "applesauce-core";
|
|
2
|
+
import { mergeRelaySets } from "applesauce-core/helpers";
|
|
3
|
+
import { kinds } from "nostr-tools";
|
|
4
|
+
import { EMPTY, identity, merge } from "rxjs";
|
|
5
|
+
import { makeCacheRequest, wrapCacheRequest } from "../helpers/cache.js";
|
|
6
|
+
import { wrapUpstreamPool } from "../helpers/upstream.js";
|
|
7
|
+
/** A list of NIP-51 list kinds that most clients will use */
|
|
8
|
+
export const COMMON_LIST_KINDS = [kinds.Contacts, kinds.Mutelist, kinds.Pinlist, kinds.BookmarkList];
|
|
9
|
+
/** A list of NIP-51 set kinds that most clients will use */
|
|
10
|
+
export const COMMON_SET_KINDS = [kinds.Bookmarksets, kinds.Followsets];
|
|
11
|
+
/**
|
|
12
|
+
* A special address loader that can request addressable events without specifying the identifier
|
|
13
|
+
* @todo this does not have a buffer, it may be useful to have one
|
|
14
|
+
*/
|
|
15
|
+
export function createUserListsLoader(pool, opts) {
|
|
16
|
+
const request = wrapUpstreamPool(pool);
|
|
17
|
+
const cacheRequest = opts?.cacheRequest ? wrapCacheRequest(opts.cacheRequest) : undefined;
|
|
18
|
+
return (user) => {
|
|
19
|
+
const filter = {
|
|
20
|
+
kinds: opts?.kinds || [...COMMON_LIST_KINDS, ...COMMON_SET_KINDS],
|
|
21
|
+
authors: [user.pubkey],
|
|
22
|
+
};
|
|
23
|
+
// Merge extra relays with user relays
|
|
24
|
+
const relays = opts?.extraRelays ? mergeRelaySets(user.relays, opts.extraRelays) : user.relays;
|
|
25
|
+
return merge(
|
|
26
|
+
// Load from cache
|
|
27
|
+
cacheRequest ? makeCacheRequest(cacheRequest, [filter]) : EMPTY,
|
|
28
|
+
// Load from relays
|
|
29
|
+
relays ? request(relays, [filter]) : EMPTY).pipe(
|
|
30
|
+
// If event store is set, deduplicate events
|
|
31
|
+
opts?.eventStore ? mapEventsToStore(opts.eventStore) : identity);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
import { UpstreamPool } from "../types.js";
|
|
4
|
+
import { TagValueLoaderOptions } from "./tag-value-loader.js";
|
|
5
|
+
/** A loader that takes an event and returns zaps */
|
|
6
|
+
export type ZapsLoader = (event: NostrEvent, relays?: string[]) => Observable<NostrEvent>;
|
|
7
|
+
export type ZapsLoaderOptions = Omit<TagValueLoaderOptions, "kinds"> & {
|
|
8
|
+
/** Whether to request zaps from the relays the event was seen on ( default true ) */
|
|
9
|
+
useSeenRelays?: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** Creates a loader that loads zap events for a given event */
|
|
12
|
+
export declare function createZapsLoader(pool: UpstreamPool, opts?: ZapsLoaderOptions): ZapsLoader;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getReplaceableAddress, getSeenRelays, isReplaceable, mergeRelaySets } from "applesauce-core/helpers";
|
|
2
|
+
import { kinds } from "nostr-tools";
|
|
3
|
+
import { wrapUpstreamPool } from "../helpers/upstream.js";
|
|
4
|
+
import { createTagValueLoader } from "./tag-value-loader.js";
|
|
5
|
+
/** Creates a loader that loads zap events for a given event */
|
|
6
|
+
export function createZapsLoader(pool, opts) {
|
|
7
|
+
const request = wrapUpstreamPool(pool);
|
|
8
|
+
const eventLoader = createTagValueLoader(request, "e", { ...opts, kinds: [kinds.Zap] });
|
|
9
|
+
const addressableLoader = createTagValueLoader(request, "a", { ...opts, kinds: [kinds.Zap] });
|
|
10
|
+
// Return diffrent loaders depending on if the event is addressable
|
|
11
|
+
return (event, relays) => {
|
|
12
|
+
if (opts?.useSeenRelays ?? true)
|
|
13
|
+
relays = mergeRelaySets(relays, getSeenRelays(event));
|
|
14
|
+
return isReplaceable(event.kind)
|
|
15
|
+
? addressableLoader({ value: getReplaceableAddress(event), relays })
|
|
16
|
+
: eventLoader({ value: event.id, relays });
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { OperatorFunction } from "rxjs";
|
|
2
|
+
/**
|
|
3
|
+
* Completes the observable when an EOSE message is received
|
|
4
|
+
* @deprecated request methods passed to loaders should complete on their own
|
|
5
|
+
*/
|
|
6
|
+
export declare function completeOnEOSE<T extends unknown>(): OperatorFunction<T | "EOSE", T>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { takeWhile } from "rxjs";
|
|
2
|
+
/**
|
|
3
|
+
* Completes the observable when an EOSE message is received
|
|
4
|
+
* @deprecated request methods passed to loaders should complete on their own
|
|
5
|
+
*/
|
|
6
|
+
export function completeOnEOSE() {
|
|
7
|
+
return (source) => source.pipe(takeWhile((m) => m !== "EOSE", false));
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Observable, OperatorFunction } from "rxjs";
|
|
2
|
+
/** Creates an observable that runs a generator and flattes the results */
|
|
3
|
+
export declare function fromGenerator<Result>(generator: Generator<Observable<Result> | Result, void, Result[]> | AsyncGenerator<Observable<Result> | Result, void, Result[]>): Observable<Result>;
|
|
4
|
+
/** Wraps a generator function to return an observable */
|
|
5
|
+
export declare function wrapGeneratorFunction<Args extends unknown[], Result>(constructor: (...args: Args) => Generator<Observable<Result> | Result, void, Result[]> | AsyncGenerator<Observable<Result> | Result, void, Result[]>): (...args: Args) => Observable<Result>;
|
|
6
|
+
/** Runs a generator for each value in sequence waiting for each to complete before running the next */
|
|
7
|
+
export declare function concatGeneratorMap<Input, Result>(createGenerator: (value: Input) => Generator<Observable<Result> | Result, void, Result[]> | AsyncGenerator<Observable<Result> | Result, void, Result[]>): OperatorFunction<Input, Result>;
|
|
8
|
+
/** Runs a generator for each value, canceling the current generator if a new value is received */
|
|
9
|
+
export declare function switchGeneratorMap<Input, Result>(createGenerator: (value: Input) => Generator<Observable<Result> | Result, void, Result[]> | AsyncGenerator<Observable<Result> | Result, void, Result[]>): OperatorFunction<Input, Result>;
|
|
10
|
+
/** Keeps retrying a value until the generator returns */
|
|
11
|
+
export declare function generator<Input, Result>(createGenerator: (value: Input) => Generator<Observable<Result> | Result, void, Result[]> | AsyncGenerator<Observable<Result> | Result, void, Result[]>,
|
|
12
|
+
/** @deprecated merge with NEVER if no completion is needed */
|
|
13
|
+
shouldComplete?: boolean): OperatorFunction<Input, Result>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { concatMap, EMPTY, isObservable, mergeMap, mergeWith, NEVER, Observable, share, switchMap, } from "rxjs";
|
|
2
|
+
/** Creates an observable that runs a generator and flattes the results */
|
|
3
|
+
export function fromGenerator(generator) {
|
|
4
|
+
return new Observable((observer) => {
|
|
5
|
+
let sub;
|
|
6
|
+
const nextSequence = (prevResults) => {
|
|
7
|
+
const p = prevResults ? generator.next(prevResults) : generator.next();
|
|
8
|
+
const handleResult = (result) => {
|
|
9
|
+
// generator complete, exit
|
|
10
|
+
if (result.done) {
|
|
11
|
+
// Ignore result.value here because its the return value and we only want the yield values
|
|
12
|
+
return observer.complete();
|
|
13
|
+
}
|
|
14
|
+
const results = [];
|
|
15
|
+
if (isObservable(result.value)) {
|
|
16
|
+
sub = result.value.subscribe({
|
|
17
|
+
next: (v) => {
|
|
18
|
+
// track results and pass along values
|
|
19
|
+
results.push(v);
|
|
20
|
+
observer.next(v);
|
|
21
|
+
},
|
|
22
|
+
error: (err) => {
|
|
23
|
+
observer.error(err);
|
|
24
|
+
},
|
|
25
|
+
complete: () => {
|
|
26
|
+
// run next step
|
|
27
|
+
nextSequence(results);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
results.push(result.value);
|
|
33
|
+
observer.next(result.value);
|
|
34
|
+
nextSequence(results);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
// if its an async generator, wait for the promise
|
|
38
|
+
if (p instanceof Promise)
|
|
39
|
+
p.then(handleResult, (err) => observer.error(err));
|
|
40
|
+
else
|
|
41
|
+
handleResult(p);
|
|
42
|
+
};
|
|
43
|
+
// start running steps
|
|
44
|
+
nextSequence();
|
|
45
|
+
// Attempt to cleanup
|
|
46
|
+
return () => sub?.unsubscribe();
|
|
47
|
+
}).pipe(
|
|
48
|
+
// Only create a single instance of the observalbe to avoice race conditions
|
|
49
|
+
share());
|
|
50
|
+
}
|
|
51
|
+
/** Wraps a generator function to return an observable */
|
|
52
|
+
export function wrapGeneratorFunction(constructor) {
|
|
53
|
+
return (...args) => fromGenerator(constructor(...args));
|
|
54
|
+
}
|
|
55
|
+
/** Runs a generator for each value in sequence waiting for each to complete before running the next */
|
|
56
|
+
export function concatGeneratorMap(createGenerator) {
|
|
57
|
+
return (source) => source.pipe(concatMap((value) => fromGenerator(createGenerator(value))));
|
|
58
|
+
}
|
|
59
|
+
/** Runs a generator for each value, canceling the current generator if a new value is received */
|
|
60
|
+
export function switchGeneratorMap(createGenerator) {
|
|
61
|
+
return (source) => source.pipe(switchMap((value) => fromGenerator(createGenerator(value))));
|
|
62
|
+
}
|
|
63
|
+
/** Keeps retrying a value until the generator returns */
|
|
64
|
+
export function generator(createGenerator,
|
|
65
|
+
/** @deprecated merge with NEVER if no completion is needed */
|
|
66
|
+
shouldComplete = true) {
|
|
67
|
+
return (source) => source.pipe(
|
|
68
|
+
// Run the generator for each value
|
|
69
|
+
mergeMap((value) => fromGenerator(createGenerator(value))),
|
|
70
|
+
// Merge with NEVER if shouldComplete is false
|
|
71
|
+
mergeWith(shouldComplete ? EMPTY : NEVER));
|
|
72
|
+
}
|
package/dist/operators/index.js
CHANGED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
/** A flexible method for requesting events from a cache */
|
|
4
|
+
export type CacheRequest = (filters: Filter[]) => Observable<NostrEvent> | Promise<NostrEvent | NostrEvent[]> | NostrEvent | NostrEvent[];
|
|
5
|
+
/** A flexible type for the upstream relay pool */
|
|
6
|
+
export type UpstreamPool = NostrRequest | {
|
|
7
|
+
request: NostrRequest;
|
|
8
|
+
};
|
|
9
|
+
/** A method for requesting events from a relay or cache` */
|
|
10
|
+
export type FilterRequest = (filters: Filter[]) => Observable<NostrEvent>;
|
|
11
|
+
/** A method for requesting events from multiple relays */
|
|
12
|
+
export type NostrRequest = (relays: string[], filters: Filter[]) => Observable<NostrEvent>;
|
|
13
|
+
/** A filter that is does not have a since or until */
|
|
14
|
+
export type TimelessFilter = Omit<Filter, "since" | "until">;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-loaders",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.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,18 +53,18 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"applesauce-core": "^0.
|
|
56
|
+
"applesauce-core": "^2.0.0",
|
|
57
57
|
"nanoid": "^5.0.9",
|
|
58
|
-
"nostr-tools": "^2.
|
|
59
|
-
"rx-nostr": "^3.5.0",
|
|
58
|
+
"nostr-tools": "^2.13",
|
|
60
59
|
"rxjs": "^7.8.1"
|
|
61
60
|
},
|
|
62
61
|
"devDependencies": {
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
62
|
+
"@hirez_io/observer-spy": "^2.2.0",
|
|
63
|
+
"applesauce-signers": "^2.0.0",
|
|
64
|
+
"typescript": "^5.8.3",
|
|
65
|
+
"vitest": "^3.2.3",
|
|
66
66
|
"vitest-nostr": "^0.4.1",
|
|
67
|
-
"vitest-websocket-mock": "^0.
|
|
67
|
+
"vitest-websocket-mock": "^0.5.0"
|
|
68
68
|
},
|
|
69
69
|
"funding": {
|
|
70
70
|
"type": "lightning",
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { createFiltersFromAddressPointers } from "../address-pointer.js";
|
|
3
|
-
import { kinds } from "nostr-tools";
|
|
4
|
-
describe("address pointer helpers", () => {
|
|
5
|
-
describe("createFiltersFromAddressPointers", () => {
|
|
6
|
-
it("should separate replaceable and parameterized replaceable pointers", () => {
|
|
7
|
-
expect(createFiltersFromAddressPointers([
|
|
8
|
-
{ kind: kinds.BookmarkList, pubkey: "pubkey" },
|
|
9
|
-
{ kind: kinds.Metadata, pubkey: "pubkey" },
|
|
10
|
-
{ kind: kinds.Metadata, pubkey: "pubkey2" },
|
|
11
|
-
{ kind: kinds.Bookmarksets, identifier: "funny", pubkey: "pubkey" },
|
|
12
|
-
])).toEqual(expect.arrayContaining([
|
|
13
|
-
{ kinds: [kinds.Metadata], authors: ["pubkey", "pubkey2"] },
|
|
14
|
-
{ kinds: [kinds.BookmarkList], authors: ["pubkey"] },
|
|
15
|
-
{ "#d": ["funny"], authors: ["pubkey"], kinds: [kinds.Bookmarksets] },
|
|
16
|
-
]));
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
});
|
package/dist/helpers/rx-nostr.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { DnsIdentityLoader } from "../dns-identity-loader.js";
|
|
3
|
-
import { unixNow } from "applesauce-core/helpers";
|
|
4
|
-
import { IdentityStatus } from "../../helpers/dns-identity.js";
|
|
5
|
-
let loader;
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
loader = new DnsIdentityLoader();
|
|
8
|
-
});
|
|
9
|
-
describe("fetch", () => {
|
|
10
|
-
it("should not assign this in fetch method", async () => {
|
|
11
|
-
const loader = new DnsIdentityLoader();
|
|
12
|
-
let that = undefined;
|
|
13
|
-
function custom() {
|
|
14
|
-
//@ts-expect-error
|
|
15
|
-
that = this;
|
|
16
|
-
throw new Error("not implemented");
|
|
17
|
-
}
|
|
18
|
-
// @ts-expect-error
|
|
19
|
-
loader.fetch = custom;
|
|
20
|
-
await loader.fetchIdentity("_", "hzrd149.com");
|
|
21
|
-
expect(that).not.toBe(loader);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
describe("requestIdentity", () => {
|
|
25
|
-
it("should load from cache first", async () => {
|
|
26
|
-
const cache = {
|
|
27
|
-
save: vi.fn().mockResolvedValue(undefined),
|
|
28
|
-
load: vi.fn().mockResolvedValue({
|
|
29
|
-
name: "_",
|
|
30
|
-
domain: "hzrd149.com",
|
|
31
|
-
pubkey: "pubkey",
|
|
32
|
-
checked: unixNow(),
|
|
33
|
-
status: IdentityStatus.Found,
|
|
34
|
-
}),
|
|
35
|
-
};
|
|
36
|
-
loader.cache = cache;
|
|
37
|
-
loader.fetch = vi.fn().mockRejectedValue(new Error("error"));
|
|
38
|
-
await loader.requestIdentity("_", "hzrd149.com");
|
|
39
|
-
expect(cache.load).toHaveBeenCalledWith("_@hzrd149.com");
|
|
40
|
-
expect(loader.fetch).not.toHaveBeenCalled();
|
|
41
|
-
});
|
|
42
|
-
it("should fetch if cache is too old", async () => {
|
|
43
|
-
const cache = {
|
|
44
|
-
save: vi.fn().mockResolvedValue(undefined),
|
|
45
|
-
load: vi.fn().mockResolvedValue({
|
|
46
|
-
name: "_",
|
|
47
|
-
domain: "hzrd149.com",
|
|
48
|
-
pubkey: "pubkey",
|
|
49
|
-
checked: unixNow() - 60 * 60 * 24 * 7 * 2,
|
|
50
|
-
status: IdentityStatus.Found,
|
|
51
|
-
}),
|
|
52
|
-
};
|
|
53
|
-
loader.cache = cache;
|
|
54
|
-
loader.fetch = vi.fn().mockRejectedValue(new Error("error"));
|
|
55
|
-
await loader.requestIdentity("_", "hzrd149.com");
|
|
56
|
-
expect(cache.load).toHaveBeenCalledWith("_@hzrd149.com");
|
|
57
|
-
expect(loader.fetch).toHaveBeenCalled();
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { expect, it } from "vitest";
|
|
2
|
-
// import { createMockRelay, MockRelay } from "vitest-nostr";
|
|
3
|
-
// import { createRxNostr } from "rx-nostr";
|
|
4
|
-
// import { verifier } from "rx-nostr-crypto";
|
|
5
|
-
// import { RelayTimelineLoader } from "./relay-timeline-loader.js";
|
|
6
|
-
// let relay: MockRelay;
|
|
7
|
-
// beforeEach(async () => {
|
|
8
|
-
// relay = createMockRelay("ws://localhost:1234");
|
|
9
|
-
// });
|
|
10
|
-
it("should complete when 0 events are returned", async () => {
|
|
11
|
-
// const rxNostr = createRxNostr({ verifier });
|
|
12
|
-
// const loader = new RelayTimelineLoader(rxNostr, "ws://localhost:1234", [{ kinds: [1] }]);
|
|
13
|
-
// let received = 0;
|
|
14
|
-
// loader.subscribe(() => received++);
|
|
15
|
-
// // load first page
|
|
16
|
-
// loader.next(100);
|
|
17
|
-
// expect(loader.loading).toBe(true);
|
|
18
|
-
// await relay.connected;
|
|
19
|
-
// await expect(relay).toReceiveREQ();
|
|
20
|
-
// relay.emitEVENT(loader.id, faker.event({ kind: 1 }));
|
|
21
|
-
// relay.emitEOSE(loader.id);
|
|
22
|
-
// await new Promise((res) => setTimeout(res, 0));
|
|
23
|
-
// expect(received).toBe(1);
|
|
24
|
-
// expect(loader.loading).toBe(false);
|
|
25
|
-
expect(true).toBeTruthy();
|
|
26
|
-
});
|
|
@@ -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,22 +0,0 @@
|
|
|
1
|
-
import { EventPacket } from "rx-nostr";
|
|
2
|
-
import { BehaviorSubject } from "rxjs";
|
|
3
|
-
import { logger } from "applesauce-core";
|
|
4
|
-
import { CacheRequest, Loader } from "./loader.js";
|
|
5
|
-
import { TimelessFilter } from "./relay-timeline-loader.js";
|
|
6
|
-
export type CacheTimelineLoaderOptions = {
|
|
7
|
-
/** default number of events to request in each batch */
|
|
8
|
-
limit?: number;
|
|
9
|
-
};
|
|
10
|
-
/** A loader that can be used to load a timeline in chunks */
|
|
11
|
-
export declare class CacheTimelineLoader extends Loader<number | void, EventPacket> {
|
|
12
|
-
filters: TimelessFilter[];
|
|
13
|
-
id: string;
|
|
14
|
-
loading$: BehaviorSubject<boolean>;
|
|
15
|
-
get loading(): boolean;
|
|
16
|
-
/** current "until" timestamp */
|
|
17
|
-
cursor: number;
|
|
18
|
-
/** set to true when 0 events are returned from last batch */
|
|
19
|
-
eose: boolean;
|
|
20
|
-
protected log: typeof logger;
|
|
21
|
-
constructor(cacheRequest: CacheRequest, filters: TimelessFilter[], opts?: CacheTimelineLoaderOptions);
|
|
22
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { BehaviorSubject, filter, map, mergeMap, tap } from "rxjs";
|
|
2
|
-
import { markFromCache, unixNow } from "applesauce-core/helpers";
|
|
3
|
-
import { logger } from "applesauce-core";
|
|
4
|
-
import { nanoid } from "nanoid";
|
|
5
|
-
import { Loader } from "./loader.js";
|
|
6
|
-
/** A loader that can be used to load a timeline in chunks */
|
|
7
|
-
export class CacheTimelineLoader extends Loader {
|
|
8
|
-
filters;
|
|
9
|
-
id = nanoid(8);
|
|
10
|
-
loading$ = new BehaviorSubject(false);
|
|
11
|
-
get loading() {
|
|
12
|
-
return this.loading$.value;
|
|
13
|
-
}
|
|
14
|
-
/** current "until" timestamp */
|
|
15
|
-
cursor = Infinity;
|
|
16
|
-
/** set to true when 0 events are returned from last batch */
|
|
17
|
-
eose = false;
|
|
18
|
-
log = logger.extend("CacheTimelineLoader");
|
|
19
|
-
constructor(cacheRequest, filters, opts) {
|
|
20
|
-
super((source) => source.pipe(filter(() => !this.loading && !this.eose), map((limit) => {
|
|
21
|
-
// build next batch filters
|
|
22
|
-
return filters.map((filter) => ({
|
|
23
|
-
limit: limit || opts?.limit,
|
|
24
|
-
...filter,
|
|
25
|
-
// limit curser to now
|
|
26
|
-
until: Math.min(unixNow(), this.cursor),
|
|
27
|
-
}));
|
|
28
|
-
}),
|
|
29
|
-
// ignore empty filters
|
|
30
|
-
filter((filters) => filters.length > 0), mergeMap((filters) => {
|
|
31
|
-
// make batch request
|
|
32
|
-
let count = 0;
|
|
33
|
-
this.loading$.next(true);
|
|
34
|
-
this.log(`Next batch starting at ${filters[0].until} limit ${filters[0].limit}`);
|
|
35
|
-
return cacheRequest(filters).pipe(tap({
|
|
36
|
-
next: (event) => {
|
|
37
|
-
// mark event from cache
|
|
38
|
-
markFromCache(event);
|
|
39
|
-
// update cursor when event is received
|
|
40
|
-
this.cursor = Math.min(event.created_at - 1, this.cursor);
|
|
41
|
-
count++;
|
|
42
|
-
},
|
|
43
|
-
complete: () => {
|
|
44
|
-
// set loading to false when batch completes
|
|
45
|
-
this.loading$.next(false);
|
|
46
|
-
// set eose if no events where returned
|
|
47
|
-
if (count === 0) {
|
|
48
|
-
this.eose = true;
|
|
49
|
-
this.log(`Got ${count} event, Complete`);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
this.log(`Finished batch, got ${count} events`);
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
}), map((event) => ({ event, from: "", subId: "cache-timeline-loader", type: "EVENT" })));
|
|
56
|
-
})));
|
|
57
|
-
this.filters = filters;
|
|
58
|
-
// create a unique logger for this instance
|
|
59
|
-
this.log = this.log.extend("cache");
|
|
60
|
-
}
|
|
61
|
-
}
|