applesauce-loaders 1.0.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 (66) hide show
  1. package/README.md +204 -65
  2. package/dist/helpers/address-pointer.d.ts +2 -2
  3. package/dist/helpers/address-pointer.js +8 -10
  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 +1 -0
  12. package/dist/helpers/pointer.js +1 -0
  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 -8
  22. package/dist/loaders/index.js +8 -8
  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 +16 -18
  28. package/dist/loaders/tag-value-loader.js +72 -71
  29. package/dist/loaders/timeline-loader.d.ts +23 -21
  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 +4 -1
  36. package/dist/operators/complete-on-eose.js +4 -1
  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 +6 -4
  43. package/dist/helpers/__tests__/address-pointer.test.js +0 -19
  44. package/dist/loaders/__tests__/dns-identity-loader.test.d.ts +0 -1
  45. package/dist/loaders/__tests__/dns-identity-loader.test.js +0 -59
  46. package/dist/loaders/__tests__/relay-timeline-loader.test.d.ts +0 -1
  47. package/dist/loaders/__tests__/relay-timeline-loader.test.js +0 -26
  48. package/dist/loaders/cache-timeline-loader.d.ts +0 -22
  49. package/dist/loaders/cache-timeline-loader.js +0 -61
  50. package/dist/loaders/loader.d.ts +0 -22
  51. package/dist/loaders/loader.js +0 -22
  52. package/dist/loaders/relay-timeline-loader.d.ts +0 -23
  53. package/dist/loaders/relay-timeline-loader.js +0 -71
  54. package/dist/loaders/replaceable-loader.d.ts +0 -27
  55. package/dist/loaders/replaceable-loader.js +0 -104
  56. package/dist/loaders/single-event-loader.d.ts +0 -32
  57. package/dist/loaders/single-event-loader.js +0 -74
  58. package/dist/loaders/user-sets-loader.d.ts +0 -33
  59. package/dist/loaders/user-sets-loader.js +0 -59
  60. package/dist/operators/__tests__/distinct-relays.test.d.ts +0 -1
  61. package/dist/operators/__tests__/distinct-relays.test.js +0 -75
  62. package/dist/operators/__tests__/generator-sequence.test.d.ts +0 -1
  63. package/dist/operators/__tests__/generator-sequence.test.js +0 -38
  64. package/dist/operators/generator-sequence.d.ts +0 -3
  65. package/dist/operators/generator-sequence.js +0 -53
  66. /package/dist/{helpers/__tests__/address-pointer.test.d.ts → types.js} +0 -0
@@ -1,71 +0,0 @@
1
- import { logger } from "applesauce-core";
2
- import { unixNow } from "applesauce-core/helpers";
3
- import { nanoid } from "nanoid";
4
- import { BehaviorSubject, filter, map, Observable } from "rxjs";
5
- import { completeOnEOSE } from "../operators/complete-on-eose.js";
6
- import { Loader } from "./loader.js";
7
- /** A loader that can be used to load a timeline in chunks */
8
- export class RelayTimelineLoader extends Loader {
9
- relay;
10
- filters;
11
- id = nanoid(8);
12
- loading$ = new BehaviorSubject(false);
13
- get loading() {
14
- return this.loading$.value;
15
- }
16
- /** current "until" timestamp */
17
- cursor = Infinity;
18
- /** if the timeline is complete */
19
- complete = false;
20
- log = logger.extend("RelayTimelineLoader");
21
- constructor(request, relay, filters, opts) {
22
- super((source) => new Observable((observer) => {
23
- return source
24
- .pipe(filter(() => !this.loading && !this.complete), map((limit) => {
25
- // build next batch filters
26
- return filters.map((filter) => ({
27
- limit: limit || opts?.limit,
28
- ...filter,
29
- // limit curser to now
30
- until: Math.min(unixNow(), this.cursor),
31
- }));
32
- }),
33
- // ignore empty filters
34
- filter((filters) => filters.length > 0))
35
- .subscribe((filters) => {
36
- // make batch request
37
- let count = 0;
38
- this.loading$.next(true);
39
- this.log(`Next batch starting at ${filters[0].until} limit ${filters[0].limit}`);
40
- request([relay], filters)
41
- .pipe(completeOnEOSE())
42
- .subscribe({
43
- next: (event) => {
44
- // update cursor when event is received
45
- this.cursor = Math.min(event.created_at - 1, this.cursor);
46
- count++;
47
- // forward packet
48
- observer.next(event);
49
- },
50
- error: (err) => observer.error(err),
51
- complete: () => {
52
- // set loading to false when batch completes
53
- this.loading$.next(false);
54
- // set complete the observable if 0 events where returned
55
- if (count === 0) {
56
- observer.complete();
57
- this.log(`Got ${count} event, Complete`);
58
- }
59
- else {
60
- this.log(`Finished batch, got ${count} events`);
61
- }
62
- },
63
- });
64
- });
65
- }));
66
- this.relay = relay;
67
- this.filters = filters;
68
- // create a unique logger for this instance
69
- this.log = this.log.extend(relay);
70
- }
71
- }
@@ -1,27 +0,0 @@
1
- import { logger } from "applesauce-core";
2
- import { NostrEvent } from "nostr-tools";
3
- import { LoadableAddressPointer } from "../helpers/address-pointer.js";
4
- import { CacheRequest, Loader, NostrRequest } from "./loader.js";
5
- export type ReplaceableLoaderOptions = {
6
- /**
7
- * Time interval to buffer requests in ms
8
- * @default 1000
9
- */
10
- bufferTime?: number;
11
- /** A method used to load events from a local cache */
12
- cacheRequest?: CacheRequest;
13
- /** Fallback lookup relays to check when event cant be found */
14
- lookupRelays?: string[];
15
- /** An array of relays to always fetch from */
16
- extraRelays?: string[];
17
- };
18
- export declare class ReplaceableLoader extends Loader<LoadableAddressPointer, NostrEvent> {
19
- log: typeof logger;
20
- /** A method used to load events from a local cache */
21
- cacheRequest?: CacheRequest;
22
- /** Fallback lookup relays to check when event cant be found */
23
- lookupRelays?: string[];
24
- /** An array of relays to always fetch from */
25
- extraRelays?: string[];
26
- constructor(request: NostrRequest, opts?: ReplaceableLoaderOptions);
27
- }
@@ -1,104 +0,0 @@
1
- import { logger } from "applesauce-core";
2
- import { getEventUID, markFromCache, mergeRelaySets } from "applesauce-core/helpers";
3
- import { nanoid } from "nanoid";
4
- import { bufferTime, filter, map, tap } from "rxjs";
5
- import { consolidateAddressPointers, createFiltersFromAddressPointers, getAddressPointerId, getRelaysFromPointers, isLoadableAddressPointer, } from "../helpers/address-pointer.js";
6
- import { completeOnEOSE } from "../operators/complete-on-eose.js";
7
- import { distinctRelaysBatch } from "../operators/distinct-relays.js";
8
- import { generatorSequence } from "../operators/generator-sequence.js";
9
- import { Loader } from "./loader.js";
10
- /** A generator that tries to load the address pointers from the cache first, then tries the relays */
11
- function* cacheFirstSequence(request, pointers, log, opts) {
12
- const id = nanoid(4);
13
- let remaining = Array.from(pointers);
14
- const pointerRelays = Array.from(getRelaysFromPointers(pointers));
15
- // handle previous step results and decide if to exit
16
- const handleResults = (results) => {
17
- if (results.length) {
18
- const coordinates = new Set(results.map((event) => getEventUID(event)));
19
- // if there where results, filter out any pointers that where found
20
- remaining = remaining.filter((pointer) => {
21
- const found = coordinates.has(getAddressPointerId(pointer));
22
- if (found && pointer.force !== true)
23
- return false;
24
- else
25
- return true;
26
- });
27
- // If there are none left, complete
28
- if (remaining.length === 0) {
29
- log(`[${id}] Complete`);
30
- return true;
31
- }
32
- }
33
- return false;
34
- };
35
- // first attempt, load from cache relays
36
- if (opts?.cacheRequest) {
37
- log(`[${id}] Checking cache`, remaining);
38
- const filters = createFiltersFromAddressPointers(remaining);
39
- const results = yield opts.cacheRequest(filters).pipe(
40
- // mark the event as from the cache
41
- tap((event) => markFromCache(event)));
42
- if (handleResults(results))
43
- return;
44
- }
45
- // load from pointer relays and extra relays
46
- const mergedRelays = mergeRelaySets(pointerRelays, opts?.extraRelays);
47
- if (mergedRelays.length > 0) {
48
- log(`[${id}] Requesting`, mergedRelays, remaining);
49
- const filters = createFiltersFromAddressPointers(remaining);
50
- const results = yield request(mergedRelays, filters).pipe(completeOnEOSE());
51
- if (handleResults(results))
52
- return;
53
- }
54
- // finally load from lookup relays
55
- if (opts?.lookupRelays) {
56
- // make sure we aren't asking a relay twice
57
- const relays = opts.lookupRelays.filter((r) => !pointerRelays.includes(r));
58
- if (relays.length > 0) {
59
- log(`[${id}] Request from lookup`, relays, remaining);
60
- const filters = createFiltersFromAddressPointers(remaining);
61
- const results = yield request(relays, filters).pipe(completeOnEOSE());
62
- if (handleResults(results))
63
- return;
64
- }
65
- }
66
- }
67
- export class ReplaceableLoader extends Loader {
68
- log = logger.extend("ReplaceableLoader");
69
- /** A method used to load events from a local cache */
70
- cacheRequest;
71
- /** Fallback lookup relays to check when event cant be found */
72
- lookupRelays;
73
- /** An array of relays to always fetch from */
74
- extraRelays;
75
- constructor(request, opts) {
76
- super((source) => {
77
- return source.pipe(
78
- // filter out invalid pointers
79
- filter(isLoadableAddressPointer),
80
- // buffer on time
81
- bufferTime(opts?.bufferTime ?? 1000),
82
- // ignore empty buffers
83
- filter((buffer) => buffer.length > 0),
84
- // only fetch from each relay once
85
- distinctRelaysBatch(getAddressPointerId),
86
- // consolidate buffered pointers
87
- map(consolidateAddressPointers),
88
- // ignore empty buffer
89
- filter((buffer) => buffer.length > 0),
90
- // check cache, relays, lookup relays in that order
91
- generatorSequence((pointers) => cacheFirstSequence(request, pointers, this.log, {
92
- cacheRequest: this.cacheRequest,
93
- lookupRelays: this.lookupRelays,
94
- extraRelays: this.extraRelays,
95
- }),
96
- // there will always be more events, never complete
97
- false));
98
- });
99
- // set options
100
- this.cacheRequest = opts?.cacheRequest;
101
- this.lookupRelays = opts?.lookupRelays;
102
- this.extraRelays = opts?.extraRelays;
103
- }
104
- }
@@ -1,32 +0,0 @@
1
- import { logger } from "applesauce-core";
2
- import { NostrEvent } from "nostr-tools";
3
- import { CacheRequest, Loader, NostrRequest } from "./loader.js";
4
- export type LoadableEventPointer = {
5
- id: string;
6
- /** Relays to load from */
7
- relays?: string[];
8
- };
9
- export type SingleEventLoaderOptions = {
10
- /**
11
- * Time interval to buffer requests in ms
12
- * @default 1000
13
- */
14
- bufferTime?: number;
15
- /**
16
- * How long the loader should wait before it allows an event pointer to be refreshed from a relay
17
- * @default 60000
18
- */
19
- refreshTimeout?: number;
20
- /** A method used to load events from a local cache */
21
- cacheRequest?: CacheRequest;
22
- /** An array of relays to always fetch from */
23
- extraRelays?: string[];
24
- };
25
- export declare class SingleEventLoader extends Loader<LoadableEventPointer, NostrEvent> {
26
- log: typeof logger;
27
- /** A method used to load events from a local cache */
28
- cacheRequest?: CacheRequest;
29
- /** An array of relays to always fetch from */
30
- extraRelays?: string[];
31
- constructor(request: NostrRequest, opts?: SingleEventLoaderOptions);
32
- }
@@ -1,74 +0,0 @@
1
- import { bufferTime, filter, from, map, mergeAll, tap } from "rxjs";
2
- import { markFromCache } from "applesauce-core/helpers";
3
- import { logger } from "applesauce-core";
4
- import { nanoid } from "nanoid";
5
- import { Loader } from "./loader.js";
6
- import { generatorSequence } from "../operators/generator-sequence.js";
7
- import { distinctRelaysBatch } from "../operators/distinct-relays.js";
8
- import { groupByRelay } from "../helpers/pointer.js";
9
- import { consolidateEventPointers } from "../helpers/event-pointer.js";
10
- import { completeOnEOSE } from "../operators/complete-on-eose.js";
11
- function* cacheFirstSequence(request, pointers, opts, log) {
12
- let remaining = [...pointers];
13
- const id = nanoid(8);
14
- log = log.extend(id);
15
- const loaded = (packets) => {
16
- const ids = new Set(packets.map((p) => p.id));
17
- remaining = remaining.filter((p) => !ids.has(p.id));
18
- };
19
- if (opts?.cacheRequest) {
20
- let filter = { ids: remaining.map((e) => e.id) };
21
- const results = yield opts.cacheRequest([filter]).pipe(
22
- // mark the event as from the cache
23
- tap((event) => markFromCache(event)));
24
- if (results.length > 0) {
25
- log(`Loaded ${results.length} events from cache`);
26
- loaded(results);
27
- }
28
- }
29
- // exit early if all pointers are loaded
30
- if (remaining.length === 0)
31
- return;
32
- let byRelay = groupByRelay(remaining, opts.extraRelays);
33
- // load remaining pointers from the relays
34
- let results = yield from(Array.from(byRelay.entries()).map(([relay, pointers]) => {
35
- let filter = { ids: pointers.map((e) => e.id) };
36
- let count = 0;
37
- log(`Requesting from ${relay}`, filter.ids);
38
- return request([relay], [filter], id)
39
- .pipe(completeOnEOSE())
40
- .pipe(tap({
41
- next: () => count++,
42
- complete: () => log(`Completed ${relay}, loaded ${count} events`),
43
- }));
44
- })).pipe(mergeAll());
45
- loaded(results);
46
- if (remaining.length > 0) {
47
- // failed to find remaining
48
- log("Failed to load", remaining.map((p) => p.id));
49
- }
50
- }
51
- export class SingleEventLoader extends Loader {
52
- log = logger.extend("SingleEventLoader");
53
- /** A method used to load events from a local cache */
54
- cacheRequest;
55
- /** An array of relays to always fetch from */
56
- extraRelays;
57
- constructor(request, opts) {
58
- super((source) => source.pipe(
59
- // batch every second
60
- bufferTime(opts?.bufferTime ?? 1000),
61
- // ignore empty buffers
62
- filter((buffer) => buffer.length > 0),
63
- // only request events from relays once
64
- distinctRelaysBatch((p) => p.id, opts?.refreshTimeout ?? 60_000),
65
- // ensure there is only one of each event pointer
66
- map(consolidateEventPointers),
67
- // run the loader sequence
68
- generatorSequence((pointers) => cacheFirstSequence(request, pointers, { cacheRequest: this.cacheRequest, extraRelays: this.extraRelays }, this.log),
69
- // there will always be more events, never complete
70
- false)));
71
- this.cacheRequest = opts?.cacheRequest;
72
- this.extraRelays = opts?.extraRelays;
73
- }
74
- }
@@ -1,33 +0,0 @@
1
- import { logger } from "applesauce-core";
2
- import { NostrEvent } from "nostr-tools";
3
- import { CacheRequest, Loader, NostrRequest } from "./loader.js";
4
- export type LoadableSetPointer = {
5
- /** A replaceable kind >= 30000 & < 40000 */
6
- kind: number;
7
- pubkey: string;
8
- /** Relays to load from */
9
- relays?: string[];
10
- /** Load the sets even if it has already been loaded */
11
- force?: boolean;
12
- };
13
- export type UserSetsLoaderOptions = {
14
- /**
15
- * Time interval to buffer requests in ms
16
- * @default 1000
17
- */
18
- bufferTime?: number;
19
- /** A method used to load events from a local cache */
20
- cacheRequest?: CacheRequest;
21
- /**
22
- * How long the loader should wait before it allows an event pointer to be refreshed from a relay
23
- * @default 120000
24
- */
25
- refreshTimeout?: number;
26
- };
27
- /** A loader that can be used to load users NIP-51 sets events ( kind >= 30000 < 40000) */
28
- export declare class UserSetsLoader extends Loader<LoadableSetPointer, NostrEvent> {
29
- log: typeof logger;
30
- /** An array of relays to always fetch from */
31
- extraRelays?: string[];
32
- constructor(request: NostrRequest, opts?: UserSetsLoaderOptions);
33
- }
@@ -1,59 +0,0 @@
1
- import { logger } from "applesauce-core";
2
- import { markFromCache } from "applesauce-core/helpers";
3
- import { nanoid } from "nanoid";
4
- import { bufferTime, filter, from, map, mergeAll, tap } from "rxjs";
5
- import { consolidateAddressPointers, createFiltersFromAddressPointers } from "../helpers/address-pointer.js";
6
- import { groupByRelay } from "../helpers/pointer.js";
7
- import { completeOnEOSE } from "../operators/complete-on-eose.js";
8
- import { distinctRelaysBatch } from "../operators/distinct-relays.js";
9
- import { generatorSequence } from "../operators/generator-sequence.js";
10
- import { Loader } from "./loader.js";
11
- /** A generator that tries to load the address pointers from the cache first, then tries the relays */
12
- function* cacheFirstSequence(request, pointers, log, opts) {
13
- const id = nanoid(8);
14
- log = log.extend(id);
15
- // first attempt, load from cache relays
16
- if (opts?.cacheRequest) {
17
- log(`Checking cache`);
18
- const filters = createFiltersFromAddressPointers(pointers);
19
- const results = yield opts.cacheRequest(filters).pipe(
20
- // mark the event as from the cache
21
- tap((event) => markFromCache(event)));
22
- if (results.length > 0) {
23
- log(`Loaded ${results.length} events from cache`);
24
- }
25
- }
26
- let byRelay = groupByRelay(pointers, opts?.extraRelays);
27
- // load sets from relays
28
- yield from(Array.from(byRelay.entries()).map(([relay, pointers]) => {
29
- let filters = createFiltersFromAddressPointers(pointers);
30
- let count = 0;
31
- log(`Requesting from ${relay}`, pointers);
32
- return request([relay], filters, id).pipe(completeOnEOSE(), tap({
33
- next: () => count++,
34
- complete: () => log(`Completed ${relay}, loaded ${count} events`),
35
- }));
36
- })).pipe(mergeAll());
37
- }
38
- /** A loader that can be used to load users NIP-51 sets events ( kind >= 30000 < 40000) */
39
- export class UserSetsLoader extends Loader {
40
- log = logger.extend("UserSetsLoader");
41
- /** An array of relays to always fetch from */
42
- extraRelays;
43
- constructor(request, opts) {
44
- let options = opts || {};
45
- super((source) => source.pipe(
46
- // load first from cache
47
- bufferTime(options?.bufferTime ?? 1000),
48
- // ignore empty buffers
49
- filter((buffer) => buffer.length > 0),
50
- // only load from each relay once
51
- distinctRelaysBatch((p) => p.kind + ":" + p.pubkey, options.refreshTimeout ?? 120_000),
52
- // deduplicate address pointers
53
- map(consolidateAddressPointers),
54
- // check cache, relays, lookup relays in that order
55
- generatorSequence((pointers) => cacheFirstSequence(request, pointers, this.log, options),
56
- // there will always be more events, never complete
57
- false)));
58
- }
59
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,75 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { Subject } from "rxjs";
3
- import { distinctRelays } from "../distinct-relays.js";
4
- describe("distinctRelays", () => {
5
- it("should filter out messages with same relay within timeout window", () => {
6
- const fn = vi.fn();
7
- const source$ = new Subject();
8
- source$.pipe(distinctRelays((msg) => msg.id, 1000)).subscribe(fn);
9
- const message = {
10
- id: "123",
11
- relays: ["relay1", "relay2"],
12
- };
13
- // Send message with two relays
14
- source$.next(message);
15
- expect(fn).toHaveBeenCalledTimes(1);
16
- expect(fn).toHaveBeenCalledWith(message);
17
- // send message again
18
- source$.next({ ...message });
19
- // should not call again
20
- expect(fn).toHaveBeenCalledTimes(1);
21
- });
22
- it("should only remove duplicate relays in timeout window", () => {
23
- const fn = vi.fn();
24
- const source$ = new Subject();
25
- source$.pipe(distinctRelays((msg) => msg.id, 1000)).subscribe(fn);
26
- const message = {
27
- id: "123",
28
- relays: ["relay1", "relay2"],
29
- };
30
- // Send message with two relays
31
- source$.next(message);
32
- expect(fn).toHaveBeenCalledTimes(1);
33
- expect(fn).toHaveBeenCalledWith(message);
34
- // send message again
35
- source$.next({ id: "123", relays: ["relay1", "relay3"] });
36
- // should not call again
37
- expect(fn).toHaveBeenCalledTimes(2);
38
- expect(fn).toHaveBeenCalledWith({ id: "123", relays: ["relay3"] });
39
- });
40
- it("should filter out duplicate messages without relays in timeout", () => {
41
- const fn = vi.fn();
42
- const source$ = new Subject();
43
- source$.pipe(distinctRelays((msg) => msg.id, 1000)).subscribe(fn);
44
- const message = { id: "123" };
45
- // Send message with two relays
46
- source$.next(message);
47
- expect(fn).toHaveBeenCalledTimes(1);
48
- expect(fn).toHaveBeenCalledWith(message);
49
- // send message again
50
- source$.next({ ...message });
51
- // should not call again
52
- expect(fn).toHaveBeenCalledTimes(1);
53
- });
54
- it("should treat messages with relays severalty then messages without", () => {
55
- const fn = vi.fn();
56
- const source$ = new Subject();
57
- source$.pipe(distinctRelays((msg) => msg.id, 1000)).subscribe(fn);
58
- const withRelays = {
59
- id: "123",
60
- relays: ["relay1", "relay2"],
61
- };
62
- const withoutRelays = {
63
- id: "123",
64
- };
65
- // Send message with two relays
66
- source$.next(withoutRelays);
67
- expect(fn).toHaveBeenCalledTimes(1);
68
- expect(fn).toHaveBeenCalledWith(withoutRelays);
69
- // send message with relays
70
- source$.next(withRelays);
71
- // should not call again
72
- expect(fn).toHaveBeenCalledTimes(2);
73
- expect(fn).toHaveBeenCalledWith(withRelays);
74
- });
75
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,38 +0,0 @@
1
- import { expect, it } from "vitest";
2
- import { lastValueFrom, of, toArray } from "rxjs";
3
- import { TestScheduler } from "rxjs/testing";
4
- import { generatorSequence } from "../generator-sequence.js";
5
- let testScheduler = new TestScheduler((actual, expected) => {
6
- expect(actual).toEqual(expected);
7
- });
8
- it("should work with normal generator functions", () => {
9
- testScheduler.run(({ expectObservable }) => {
10
- function* normalGenerator(value) {
11
- yield of(value + 1);
12
- yield of(value + 2);
13
- yield of(value + 3);
14
- yield value + 4;
15
- }
16
- const source$ = of(1).pipe(generatorSequence(normalGenerator));
17
- // Define expected marble diagram
18
- const expectedMarble = "(abcd|)";
19
- const expectedValues = {
20
- a: 2,
21
- b: 3,
22
- c: 4,
23
- d: 5,
24
- };
25
- expectObservable(source$).toBe(expectedMarble, expectedValues);
26
- });
27
- });
28
- it("should work with async generator functions", async () => {
29
- async function* asyncGenerator(value) {
30
- yield of(`${value}-1`);
31
- yield of(`${value}-2`);
32
- yield of(`${value}-3`);
33
- yield `${value}-4`;
34
- }
35
- const source$ = of("test").pipe(generatorSequence(asyncGenerator));
36
- const expectedValues = ["test-1", "test-2", "test-3", "test-4"];
37
- expect(await lastValueFrom(source$.pipe(toArray()))).toEqual(expectedValues);
38
- });
@@ -1,3 +0,0 @@
1
- import { Observable, OperatorFunction } from "rxjs";
2
- /** Keeps retrying a value until the generator returns */
3
- export declare function generatorSequence<Input, Result>(createGenerator: (value: Input) => Generator<Observable<Result> | Result, void, Result[] | undefined> | AsyncGenerator<Observable<Result> | Result, void, Result[] | undefined>, shouldComplete?: boolean): OperatorFunction<Input, Result>;
@@ -1,53 +0,0 @@
1
- import { isObservable, Observable } from "rxjs";
2
- /** Keeps retrying a value until the generator returns */
3
- export function generatorSequence(createGenerator, shouldComplete = true) {
4
- return (source) => {
5
- return new Observable((observer) => {
6
- return source.subscribe((value) => {
7
- const generator = createGenerator(value);
8
- const nextSequence = (prevResults) => {
9
- const p = generator.next(prevResults);
10
- const handleResult = (result) => {
11
- // generator complete, exit
12
- if (result.done) {
13
- if (shouldComplete)
14
- observer.complete();
15
- return;
16
- }
17
- const results = [];
18
- if (isObservable(result.value)) {
19
- result.value.subscribe({
20
- next: (v) => {
21
- // track results and pass along values
22
- results.push(v);
23
- observer.next(v);
24
- },
25
- error: (err) => {
26
- observer.error(err);
27
- },
28
- complete: () => {
29
- // run next step
30
- nextSequence(results);
31
- },
32
- });
33
- }
34
- else {
35
- results.push(result.value);
36
- observer.next(result.value);
37
- nextSequence(results);
38
- }
39
- };
40
- // if its an async generator, wait for the promise
41
- if (p instanceof Promise)
42
- p.then(handleResult, (err) => {
43
- observer.error(err);
44
- });
45
- else
46
- handleResult(p);
47
- };
48
- // start running steps
49
- nextSequence();
50
- });
51
- });
52
- };
53
- }