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.
Files changed (72) hide show
  1. package/README.md +204 -57
  2. package/dist/helpers/address-pointer.d.ts +2 -2
  3. package/dist/helpers/address-pointer.js +11 -13
  4. package/dist/helpers/cache.d.ts +9 -0
  5. package/dist/helpers/cache.js +22 -0
  6. package/dist/helpers/event-pointer.js +10 -10
  7. package/dist/helpers/index.d.ts +5 -0
  8. package/dist/helpers/index.js +5 -0
  9. package/dist/helpers/loaders.d.ts +3 -0
  10. package/dist/helpers/loaders.js +27 -0
  11. package/dist/helpers/pointer.d.ts +2 -1
  12. package/dist/helpers/pointer.js +4 -2
  13. package/dist/helpers/upstream.d.ts +7 -0
  14. package/dist/helpers/upstream.js +13 -0
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +2 -1
  17. package/dist/loaders/address-loader.d.ts +37 -0
  18. package/dist/loaders/address-loader.js +94 -0
  19. package/dist/loaders/event-loader.d.ts +33 -0
  20. package/dist/loaders/event-loader.js +92 -0
  21. package/dist/loaders/index.d.ts +8 -9
  22. package/dist/loaders/index.js +8 -9
  23. package/dist/loaders/reactions-loader.d.ts +12 -0
  24. package/dist/loaders/reactions-loader.js +18 -0
  25. package/dist/loaders/social-graph.d.ts +21 -0
  26. package/dist/loaders/social-graph.js +50 -0
  27. package/dist/loaders/tag-value-loader.d.ts +19 -15
  28. package/dist/loaders/tag-value-loader.js +73 -71
  29. package/dist/loaders/timeline-loader.d.ts +24 -22
  30. package/dist/loaders/timeline-loader.js +49 -55
  31. package/dist/loaders/user-lists-loader.d.ts +26 -0
  32. package/dist/loaders/user-lists-loader.js +33 -0
  33. package/dist/loaders/zaps-loader.d.ts +12 -0
  34. package/dist/loaders/zaps-loader.js +18 -0
  35. package/dist/operators/complete-on-eose.d.ts +6 -0
  36. package/dist/operators/complete-on-eose.js +8 -0
  37. package/dist/operators/generator.d.ts +13 -0
  38. package/dist/operators/generator.js +72 -0
  39. package/dist/operators/index.d.ts +1 -1
  40. package/dist/operators/index.js +1 -1
  41. package/dist/types.d.ts +14 -0
  42. package/package.json +8 -8
  43. package/dist/helpers/__tests__/address-pointer.test.js +0 -19
  44. package/dist/helpers/rx-nostr.d.ts +0 -2
  45. package/dist/helpers/rx-nostr.js +0 -5
  46. package/dist/loaders/__tests__/dns-identity-loader.test.d.ts +0 -1
  47. package/dist/loaders/__tests__/dns-identity-loader.test.js +0 -59
  48. package/dist/loaders/__tests__/relay-timeline-loader.test.d.ts +0 -1
  49. package/dist/loaders/__tests__/relay-timeline-loader.test.js +0 -26
  50. package/dist/loaders/__tests__/request-loader.test.d.ts +0 -1
  51. package/dist/loaders/__tests__/request-loader.test.js +0 -37
  52. package/dist/loaders/cache-timeline-loader.d.ts +0 -22
  53. package/dist/loaders/cache-timeline-loader.js +0 -61
  54. package/dist/loaders/loader.d.ts +0 -20
  55. package/dist/loaders/loader.js +0 -22
  56. package/dist/loaders/relay-timeline-loader.d.ts +0 -24
  57. package/dist/loaders/relay-timeline-loader.js +0 -70
  58. package/dist/loaders/replaceable-loader.d.ts +0 -23
  59. package/dist/loaders/replaceable-loader.js +0 -106
  60. package/dist/loaders/request-loader.d.ts +0 -30
  61. package/dist/loaders/request-loader.js +0 -57
  62. package/dist/loaders/single-event-loader.d.ts +0 -26
  63. package/dist/loaders/single-event-loader.js +0 -76
  64. package/dist/loaders/user-sets-loader.d.ts +0 -31
  65. package/dist/loaders/user-sets-loader.js +0 -66
  66. package/dist/operators/__tests__/distinct-relays.test.d.ts +0 -1
  67. package/dist/operators/__tests__/distinct-relays.test.js +0 -75
  68. package/dist/operators/__tests__/generator-sequence.test.d.ts +0 -1
  69. package/dist/operators/__tests__/generator-sequence.test.js +0 -38
  70. package/dist/operators/generator-sequence.d.ts +0 -3
  71. package/dist/operators/generator-sequence.js +0 -53
  72. /package/dist/{helpers/__tests__/address-pointer.test.d.ts → types.js} +0 -0
@@ -1,56 +1,50 @@
1
- import { BehaviorSubject, combineLatest, connect, merge, tap } from "rxjs";
2
- import { logger } from "applesauce-core";
3
- import { mergeFilters } from "applesauce-core/helpers";
4
- import { nanoid } from "nanoid";
5
- import { RelayTimelineLoader } from "./relay-timeline-loader.js";
6
- import { Loader } from "./loader.js";
7
- import { CacheTimelineLoader } from "./cache-timeline-loader.js";
8
- /** A multi-relay timeline loader that can be used to load a timeline from multiple relays */
9
- export class TimelineLoader extends Loader {
10
- id = nanoid(8);
11
- loading$ = new BehaviorSubject(false);
12
- get loading() {
13
- return this.loading$.value;
14
- }
15
- requests;
16
- log = logger.extend("TimelineLoader");
17
- cache;
18
- loaders;
19
- constructor(rxNostr, requests, opts) {
20
- const loaders = new Map();
21
- // create cache loader
22
- const cache = opts?.cacheRequest
23
- ? new CacheTimelineLoader(opts.cacheRequest, [mergeFilters(...Object.values(requests).flat())], opts)
24
- : undefined;
25
- // create loaders
26
- for (const [relay, filters] of Object.entries(requests)) {
27
- loaders.set(relay, new RelayTimelineLoader(rxNostr, relay, filters, opts));
28
- }
29
- const allLoaders = cache ? [cache, ...loaders.values()] : Array.from(loaders.values());
30
- super((source) => {
31
- // observable that triggers the loaders based on cursor
32
- const trigger$ = source.pipe(tap((cursor) => {
33
- for (const loader of allLoaders) {
34
- // load the next page if cursor is past loader cursor
35
- if (!cursor || !Number.isFinite(cursor) || cursor <= loader.cursor)
36
- loader.next();
37
- }
38
- }));
39
- // observable that handles updating the loading state
40
- const loading$ = combineLatest(allLoaders.map((l) => l.loading$)).pipe(
41
- // set loading to true as long as one loader is still loading
42
- tap((loading) => this.loading$.next(loading.some((v) => v === true))));
43
- // observable that merges all the outputs of the loaders
44
- const events$ = merge(...allLoaders.map((l) => l.observable));
45
- // subscribe to all observables but only return the results of events$
46
- return merge(trigger$, loading$, events$).pipe(connect((_shared$) => events$));
47
- });
48
- this.requests = requests;
49
- this.cache = cache;
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
+ }
@@ -1,3 +1,3 @@
1
1
  export * from "./distinct-relays.js";
2
2
  export * from "./distinct-timeout.js";
3
- export * from "./generator-sequence.js";
3
+ export * from "./generator.js";
@@ -1,3 +1,3 @@
1
1
  export * from "./distinct-relays.js";
2
2
  export * from "./distinct-timeout.js";
3
- export * from "./generator-sequence.js";
3
+ export * from "./generator.js";
@@ -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.12.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.12.0",
56
+ "applesauce-core": "^2.0.0",
57
57
  "nanoid": "^5.0.9",
58
- "nostr-tools": "^2.10.4",
59
- "rx-nostr": "^3.5.0",
58
+ "nostr-tools": "^2.13",
60
59
  "rxjs": "^7.8.1"
61
60
  },
62
61
  "devDependencies": {
63
- "rx-nostr-crypto": "^3.1.3",
64
- "typescript": "^5.7.3",
65
- "vitest": "^3.0.5",
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.4.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
- });
@@ -1,2 +0,0 @@
1
- import { RxNostr } from "rx-nostr";
2
- export declare function getDefaultReadRelays(rxNostr: RxNostr): string[];
@@ -1,5 +0,0 @@
1
- export function getDefaultReadRelays(rxNostr) {
2
- return Object.entries(rxNostr.getDefaultRelays())
3
- .filter(([_, config]) => config.read)
4
- .map(([relay]) => relay);
5
- }
@@ -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,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
- }