applesauce-core 0.7.0 → 0.9.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 (46) hide show
  1. package/dist/event-store/database.d.ts +4 -41
  2. package/dist/event-store/database.js +15 -12
  3. package/dist/event-store/event-store.d.ts +1 -4
  4. package/dist/event-store/event-store.js +1 -10
  5. package/dist/helpers/bolt11.d.ts +8 -0
  6. package/dist/helpers/bolt11.js +14 -0
  7. package/dist/helpers/cache.js +9 -9
  8. package/dist/helpers/event.d.ts +9 -1
  9. package/dist/helpers/event.js +14 -1
  10. package/dist/helpers/filter.d.ts +4 -0
  11. package/dist/helpers/filter.js +9 -0
  12. package/dist/helpers/index.d.ts +2 -0
  13. package/dist/helpers/index.js +2 -0
  14. package/dist/helpers/mailboxes.d.ts +2 -8
  15. package/dist/helpers/mailboxes.js +13 -14
  16. package/dist/helpers/pointers.d.ts +1 -1
  17. package/dist/helpers/pointers.js +1 -3
  18. package/dist/helpers/profile.d.ts +2 -7
  19. package/dist/helpers/profile.js +26 -23
  20. package/dist/helpers/threading.d.ts +5 -5
  21. package/dist/helpers/threading.js +27 -15
  22. package/dist/helpers/zap.d.ts +14 -0
  23. package/dist/helpers/zap.js +66 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/observable/getValue.d.ts +1 -1
  27. package/dist/observable/getValue.js +4 -2
  28. package/dist/observable/index.d.ts +1 -2
  29. package/dist/observable/index.js +1 -2
  30. package/dist/observable/share-latest-value.d.ts +8 -0
  31. package/dist/observable/share-latest-value.js +21 -0
  32. package/dist/queries/index.d.ts +1 -0
  33. package/dist/queries/index.js +1 -0
  34. package/dist/queries/mailboxes.d.ts +2 -2
  35. package/dist/queries/mailboxes.js +3 -2
  36. package/dist/queries/profile.js +3 -2
  37. package/dist/queries/thread.js +3 -2
  38. package/dist/queries/zaps.d.ts +4 -0
  39. package/dist/queries/zaps.js +20 -0
  40. package/dist/query-store/index.d.ts +4 -5
  41. package/dist/query-store/index.js +2 -2
  42. package/package.json +10 -7
  43. package/dist/observable/stateful.d.ts +0 -10
  44. package/dist/observable/stateful.js +0 -60
  45. package/dist/observable/throttle.d.ts +0 -3
  46. package/dist/observable/throttle.js +0 -23
@@ -1,6 +1,5 @@
1
- /// <reference types="debug" />
2
- /// <reference types="zen-observable" />
3
1
  import { Filter, NostrEvent } from "nostr-tools";
2
+ import { Subject } from "rxjs";
4
3
  import { LRU } from "../helpers/lru.js";
5
4
  /**
6
5
  * An in-memory database for nostr events
@@ -14,48 +13,12 @@ export declare class Database {
14
13
  protected created_at: NostrEvent[];
15
14
  /** LRU cache of last events touched */
16
15
  events: LRU<import("nostr-tools").Event>;
17
- private insertedSignal;
18
- private updatedSignal;
19
- private deletedSignal;
20
16
  /** A stream of events inserted into the database */
21
- inserted: {
22
- subscribe(observer: ZenObservable.Observer<import("nostr-tools").Event>): ZenObservable.Subscription;
23
- subscribe(onNext: (value: import("nostr-tools").Event) => void, onError?: ((error: any) => void) | undefined, onComplete?: (() => void) | undefined): ZenObservable.Subscription;
24
- forEach(callback: (value: import("nostr-tools").Event) => void): Promise<void>;
25
- map<R>(callback: (value: import("nostr-tools").Event) => R): import("zen-observable")<R>;
26
- filter<S extends import("nostr-tools").Event>(callback: (value: import("nostr-tools").Event) => value is S): import("zen-observable")<S>;
27
- filter(callback: (value: import("nostr-tools").Event) => boolean): import("zen-observable")<import("nostr-tools").Event>;
28
- reduce(callback: (previousValue: import("nostr-tools").Event, currentValue: import("nostr-tools").Event) => import("nostr-tools").Event, initialValue?: import("nostr-tools").Event | undefined): import("zen-observable")<import("nostr-tools").Event>;
29
- reduce<R_1>(callback: (previousValue: R_1, currentValue: import("nostr-tools").Event) => R_1, initialValue?: R_1 | undefined): import("zen-observable")<R_1>;
30
- flatMap<R_2>(callback: (value: import("nostr-tools").Event) => ZenObservable.ObservableLike<R_2>): import("zen-observable")<R_2>;
31
- concat<R_3>(...observable: import("zen-observable")<R_3>[]): import("zen-observable")<R_3>;
32
- };
17
+ inserted: Subject<import("nostr-tools").Event>;
33
18
  /** A stream of events that have been updated */
34
- updated: {
35
- subscribe(observer: ZenObservable.Observer<import("nostr-tools").Event>): ZenObservable.Subscription;
36
- subscribe(onNext: (value: import("nostr-tools").Event) => void, onError?: ((error: any) => void) | undefined, onComplete?: (() => void) | undefined): ZenObservable.Subscription;
37
- forEach(callback: (value: import("nostr-tools").Event) => void): Promise<void>;
38
- map<R>(callback: (value: import("nostr-tools").Event) => R): import("zen-observable")<R>;
39
- filter<S extends import("nostr-tools").Event>(callback: (value: import("nostr-tools").Event) => value is S): import("zen-observable")<S>;
40
- filter(callback: (value: import("nostr-tools").Event) => boolean): import("zen-observable")<import("nostr-tools").Event>;
41
- reduce(callback: (previousValue: import("nostr-tools").Event, currentValue: import("nostr-tools").Event) => import("nostr-tools").Event, initialValue?: import("nostr-tools").Event | undefined): import("zen-observable")<import("nostr-tools").Event>;
42
- reduce<R_1>(callback: (previousValue: R_1, currentValue: import("nostr-tools").Event) => R_1, initialValue?: R_1 | undefined): import("zen-observable")<R_1>;
43
- flatMap<R_2>(callback: (value: import("nostr-tools").Event) => ZenObservable.ObservableLike<R_2>): import("zen-observable")<R_2>;
44
- concat<R_3>(...observable: import("zen-observable")<R_3>[]): import("zen-observable")<R_3>;
45
- };
19
+ updated: Subject<import("nostr-tools").Event>;
46
20
  /** A stream of events removed of the database */
47
- deleted: {
48
- subscribe(observer: ZenObservable.Observer<import("nostr-tools").Event>): ZenObservable.Subscription;
49
- subscribe(onNext: (value: import("nostr-tools").Event) => void, onError?: ((error: any) => void) | undefined, onComplete?: (() => void) | undefined): ZenObservable.Subscription;
50
- forEach(callback: (value: import("nostr-tools").Event) => void): Promise<void>;
51
- map<R>(callback: (value: import("nostr-tools").Event) => R): import("zen-observable")<R>;
52
- filter<S extends import("nostr-tools").Event>(callback: (value: import("nostr-tools").Event) => value is S): import("zen-observable")<S>;
53
- filter(callback: (value: import("nostr-tools").Event) => boolean): import("zen-observable")<import("nostr-tools").Event>;
54
- reduce(callback: (previousValue: import("nostr-tools").Event, currentValue: import("nostr-tools").Event) => import("nostr-tools").Event, initialValue?: import("nostr-tools").Event | undefined): import("zen-observable")<import("nostr-tools").Event>;
55
- reduce<R_1>(callback: (previousValue: R_1, currentValue: import("nostr-tools").Event) => R_1, initialValue?: R_1 | undefined): import("zen-observable")<R_1>;
56
- flatMap<R_2>(callback: (value: import("nostr-tools").Event) => ZenObservable.ObservableLike<R_2>): import("zen-observable")<R_2>;
57
- concat<R_3>(...observable: import("zen-observable")<R_3>[]): import("zen-observable")<R_3>;
58
- };
21
+ deleted: Subject<import("nostr-tools").Event>;
59
22
  get size(): number;
60
23
  protected claims: WeakMap<import("nostr-tools").Event, any>;
61
24
  /** Index helper methods */
@@ -1,6 +1,6 @@
1
1
  import { binarySearch, insertEventIntoDescendingList } from "nostr-tools/utils";
2
- import PushStream from "zen-push";
3
- import { getEventUID, getIndexableTags, getReplaceableUID } from "../helpers/event.js";
2
+ import { Subject } from "rxjs";
3
+ import { FromCacheSymbol, getEventUID, getIndexableTags, getReplaceableUID } from "../helpers/event.js";
4
4
  import { INDEXABLE_TAGS } from "./common.js";
5
5
  import { logger } from "../logger.js";
6
6
  import { LRU } from "../helpers/lru.js";
@@ -16,15 +16,12 @@ export class Database {
16
16
  created_at = [];
17
17
  /** LRU cache of last events touched */
18
18
  events = new LRU();
19
- insertedSignal = new PushStream();
20
- updatedSignal = new PushStream();
21
- deletedSignal = new PushStream();
22
19
  /** A stream of events inserted into the database */
23
- inserted = this.insertedSignal.observable;
20
+ inserted = new Subject();
24
21
  /** A stream of events that have been updated */
25
- updated = this.updatedSignal.observable;
22
+ updated = new Subject();
26
23
  /** A stream of events removed of the database */
27
- deleted = this.deletedSignal.observable;
24
+ deleted = new Subject();
28
25
  get size() {
29
26
  return this.events.size;
30
27
  }
@@ -79,8 +76,14 @@ export class Database {
79
76
  addEvent(event) {
80
77
  const uid = getEventUID(event);
81
78
  const current = this.events.get(uid);
82
- if (current && event.created_at <= current.created_at)
79
+ if (current && event.created_at <= current.created_at) {
80
+ // if this is a duplicate event, transfer some import symbols
81
+ if (current.id === event.id) {
82
+ if (event[FromCacheSymbol])
83
+ current[FromCacheSymbol] = event[FromCacheSymbol];
84
+ }
83
85
  return current;
86
+ }
84
87
  this.events.set(uid, event);
85
88
  this.getKindIndex(event.kind).add(event);
86
89
  this.getAuthorsIndex(event.pubkey).add(event);
@@ -90,13 +93,13 @@ export class Database {
90
93
  }
91
94
  }
92
95
  insertEventIntoDescendingList(this.created_at, event);
93
- this.insertedSignal.next(event);
96
+ this.inserted.next(event);
94
97
  return event;
95
98
  }
96
99
  /** Inserts and event into the database and notifies all subscriptions that the event has updated */
97
100
  updateEvent(event) {
98
101
  const inserted = this.addEvent(event);
99
- this.updatedSignal.next(inserted);
102
+ this.updated.next(inserted);
100
103
  return inserted;
101
104
  }
102
105
  /** Deletes an event from the database and notifies all subscriptions */
@@ -119,7 +122,7 @@ export class Database {
119
122
  const i = this.created_at.indexOf(event);
120
123
  this.created_at.splice(i, 1);
121
124
  this.events.delete(uid);
122
- this.deletedSignal.next(event);
125
+ this.deleted.next(event);
123
126
  return true;
124
127
  }
125
128
  /** Sets the claim on the event and touches it */
@@ -1,11 +1,8 @@
1
1
  import { Filter, NostrEvent } from "nostr-tools";
2
- import Observable from "zen-observable";
2
+ import { Observable } from "rxjs";
3
3
  import { Database } from "./database.js";
4
4
  export declare class EventStore {
5
5
  database: Database;
6
- private singles;
7
- private streams;
8
- private timelines;
9
6
  constructor();
10
7
  /** Adds an event to the database */
11
8
  add(event: NostrEvent, fromRelay?: string): import("nostr-tools").Event;
@@ -1,14 +1,11 @@
1
1
  import { insertEventIntoDescendingList } from "nostr-tools/utils";
2
- import Observable from "zen-observable";
2
+ import { Observable } from "rxjs";
3
3
  import { Database } from "./database.js";
4
4
  import { getEventUID, getReplaceableUID } from "../helpers/event.js";
5
5
  import { matchFilters } from "../helpers/filter.js";
6
6
  import { addSeenRelay } from "../helpers/relays.js";
7
7
  export class EventStore {
8
8
  database;
9
- singles = new Map();
10
- streams = new Map();
11
- timelines = new Map();
12
9
  constructor() {
13
10
  this.database = new Database();
14
11
  }
@@ -71,12 +68,10 @@ export class EventStore {
71
68
  observer.next(undefined);
72
69
  }
73
70
  });
74
- this.singles.set(observer, uid);
75
71
  return () => {
76
72
  inserted.unsubscribe();
77
73
  deleted.unsubscribe();
78
74
  updated.unsubscribe();
79
- this.singles.delete(observer);
80
75
  if (current)
81
76
  this.database.removeClaim(current, observer);
82
77
  };
@@ -160,10 +155,8 @@ export class EventStore {
160
155
  claimed.add(event);
161
156
  }
162
157
  });
163
- this.streams.set(observer, filters);
164
158
  return () => {
165
159
  sub.unsubscribe();
166
- this.streams.delete(observer);
167
160
  // remove all claims
168
161
  for (const event of claimed)
169
162
  this.database.removeClaim(event, observer);
@@ -228,9 +221,7 @@ export class EventStore {
228
221
  this.database.removeClaim(current, observer);
229
222
  }
230
223
  });
231
- this.timelines.set(observer, filters);
232
224
  return () => {
233
- this.timelines.delete(observer);
234
225
  inserted.unsubscribe();
235
226
  deleted.unsubscribe();
236
227
  updated.unsubscribe();
@@ -0,0 +1,8 @@
1
+ export type ParsedInvoice = {
2
+ paymentRequest: string;
3
+ description: string;
4
+ amount?: number;
5
+ timestamp: number;
6
+ expiry: number;
7
+ };
8
+ export declare function parseBolt11(paymentRequest: string): ParsedInvoice;
@@ -0,0 +1,14 @@
1
+ import { decode } from "light-bolt11-decoder";
2
+ export function parseBolt11(paymentRequest) {
3
+ const decoded = decode(paymentRequest);
4
+ const timestamp = decoded.sections.find((s) => s.name === "timestamp")?.value ?? 0;
5
+ const description = decoded.sections.find((s) => s.name === "description")?.value ?? "";
6
+ const amount = parseInt(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
7
+ return {
8
+ paymentRequest: decoded.paymentRequest,
9
+ description: description,
10
+ amount: amount,
11
+ timestamp: timestamp,
12
+ expiry: timestamp + decoded.expiry,
13
+ };
14
+ }
@@ -1,17 +1,17 @@
1
1
  export function getCachedValue(event, symbol) {
2
- // @ts-expect-error
3
- return event[symbol];
2
+ return Reflect.get(event, symbol);
4
3
  }
5
4
  export function setCachedValue(event, symbol, value) {
6
- // @ts-expect-error
7
- event[symbol] = value;
5
+ Reflect.set(event, symbol, value);
8
6
  }
9
7
  /** Internal method used to cache computed values on events */
10
8
  export function getOrComputeCachedValue(event, symbol, compute) {
11
- let cached = getCachedValue(event, symbol);
12
- if (!cached) {
13
- // @ts-expect-error
14
- cached = event[symbol] = compute(event);
9
+ if (Reflect.has(event, symbol)) {
10
+ return Reflect.get(event, symbol);
11
+ }
12
+ else {
13
+ const value = compute(event);
14
+ Reflect.set(event, symbol, value);
15
+ return value;
15
16
  }
16
- return cached;
17
17
  }
@@ -1,10 +1,12 @@
1
- import { NostrEvent } from "nostr-tools";
1
+ import { NostrEvent, VerifiedEvent } from "nostr-tools";
2
2
  export declare const EventUIDSymbol: unique symbol;
3
3
  export declare const EventIndexableTagsSymbol: unique symbol;
4
+ export declare const FromCacheSymbol: unique symbol;
4
5
  declare module "nostr-tools" {
5
6
  interface Event {
6
7
  [EventUIDSymbol]?: string;
7
8
  [EventIndexableTagsSymbol]?: Set<string>;
9
+ [FromCacheSymbol]?: boolean;
8
10
  }
9
11
  }
10
12
  /**
@@ -24,3 +26,9 @@ export declare function getReplaceableUID(kind: number, pubkey: string, d?: stri
24
26
  export declare function getIndexableTags(event: NostrEvent): Set<string>;
25
27
  /** Returns the second index ( tag[1] ) of the first tag that matches the name */
26
28
  export declare function getTagValue(event: NostrEvent, name: string): string | undefined;
29
+ /** Sets events verified flag without checking anything */
30
+ export declare function fakeVerifyEvent(event: NostrEvent): event is VerifiedEvent;
31
+ /** Marks an event as being from a cache */
32
+ export declare function markFromCache(event: NostrEvent): void;
33
+ /** Returns if an event was from a cache */
34
+ export declare function isFromCache(event: NostrEvent): boolean;
@@ -1,7 +1,8 @@
1
- import { kinds } from "nostr-tools";
1
+ import { kinds, verifiedSymbol } from "nostr-tools";
2
2
  import { INDEXABLE_TAGS } from "../event-store/common.js";
3
3
  export const EventUIDSymbol = Symbol.for("event-uid");
4
4
  export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
5
+ export const FromCacheSymbol = Symbol.for("from-cache");
5
6
  /**
6
7
  * Returns if a kind is replaceable ( 10000 <= n < 20000 || n == 0 || n == 3 )
7
8
  * or parameterized replaceable ( 30000 <= n < 40000 )
@@ -49,3 +50,15 @@ export function getIndexableTags(event) {
49
50
  export function getTagValue(event, name) {
50
51
  return event.tags.find((t) => t[0] === name)?.[1];
51
52
  }
53
+ /** Sets events verified flag without checking anything */
54
+ export function fakeVerifyEvent(event) {
55
+ return (event[verifiedSymbol] = true);
56
+ }
57
+ /** Marks an event as being from a cache */
58
+ export function markFromCache(event) {
59
+ event[FromCacheSymbol] = true;
60
+ }
61
+ /** Returns if an event was from a cache */
62
+ export function isFromCache(event) {
63
+ return !!event[FromCacheSymbol];
64
+ }
@@ -6,3 +6,7 @@ import { Filter, NostrEvent } from "nostr-tools";
6
6
  export declare function matchFilter(filter: Filter, event: NostrEvent): boolean;
7
7
  /** Copied from nostr-tools */
8
8
  export declare function matchFilters(filters: Filter[], event: NostrEvent): boolean;
9
+ /** Stringify filters in a predictable way */
10
+ export declare function stringifyFilter(filter: Filter | Filter[]): string;
11
+ /** Check if two filters are equal */
12
+ export declare function isFilterEqual(a: Filter | Filter[], b: Filter | Filter[]): boolean;
@@ -1,4 +1,5 @@
1
1
  import { getIndexableTags } from "./event.js";
2
+ import stringify from "json-stringify-deterministic";
2
3
  /**
3
4
  * Copied from nostr-tools and modified to use getIndexableTags
4
5
  * @see https://github.com/nbd-wtf/nostr-tools/blob/a61cde77eacc9518001f11d7f67f1a50ae05fd80/filter.ts
@@ -39,3 +40,11 @@ export function matchFilters(filters, event) {
39
40
  }
40
41
  return false;
41
42
  }
43
+ /** Stringify filters in a predictable way */
44
+ export function stringifyFilter(filter) {
45
+ return stringify(filter);
46
+ }
47
+ /** Check if two filters are equal */
48
+ export function isFilterEqual(a, b) {
49
+ return stringifyFilter(a) === stringifyFilter(b);
50
+ }
@@ -12,3 +12,5 @@ export * from "./emoji.js";
12
12
  export * from "./lru.js";
13
13
  export * from "./hashtag.js";
14
14
  export * from "./url.js";
15
+ export * from "./zap.js";
16
+ export * from "./bolt11.js";
@@ -12,3 +12,5 @@ export * from "./emoji.js";
12
12
  export * from "./lru.js";
13
13
  export * from "./hashtag.js";
14
14
  export * from "./url.js";
15
+ export * from "./zap.js";
16
+ export * from "./bolt11.js";
@@ -1,17 +1,11 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
2
  export declare const MailboxesInboxesSymbol: unique symbol;
3
3
  export declare const MailboxesOutboxesSymbol: unique symbol;
4
- declare module "nostr-tools" {
5
- interface Event {
6
- [MailboxesInboxesSymbol]?: Set<string>;
7
- [MailboxesOutboxesSymbol]?: Set<string>;
8
- }
9
- }
10
4
  /**
11
5
  * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
12
6
  */
13
- export declare function getInboxes(event: NostrEvent): Set<string>;
7
+ export declare function getInboxes(event: NostrEvent): string[];
14
8
  /**
15
9
  * Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
16
10
  */
17
- export declare function getOutboxes(event: NostrEvent): Set<string>;
11
+ export declare function getOutboxes(event: NostrEvent): string[];
@@ -1,37 +1,36 @@
1
1
  import { safeRelayUrl } from "./relays.js";
2
+ import { getOrComputeCachedValue } from "./cache.js";
2
3
  export const MailboxesInboxesSymbol = Symbol.for("mailboxes-inboxes");
3
4
  export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
4
5
  /**
5
6
  * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
6
7
  */
7
8
  export function getInboxes(event) {
8
- if (!event[MailboxesInboxesSymbol]) {
9
- const inboxes = new Set();
9
+ return getOrComputeCachedValue(event, MailboxesInboxesSymbol, () => {
10
+ const inboxes = [];
10
11
  for (const tag of event.tags) {
11
12
  if (tag[0] === "r" && tag[1] && (tag[2] === "read" || tag[2] === undefined)) {
12
13
  const url = safeRelayUrl(tag[1]);
13
- if (url)
14
- inboxes.add(url);
14
+ if (url && !inboxes.includes(url))
15
+ inboxes.push(url);
15
16
  }
16
17
  }
17
- event[MailboxesInboxesSymbol] = inboxes;
18
- }
19
- return event[MailboxesInboxesSymbol];
18
+ return inboxes;
19
+ });
20
20
  }
21
21
  /**
22
22
  * Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
23
23
  */
24
24
  export function getOutboxes(event) {
25
- if (!event[MailboxesOutboxesSymbol]) {
26
- const outboxes = new Set();
25
+ return getOrComputeCachedValue(event, MailboxesOutboxesSymbol, () => {
26
+ const outboxes = [];
27
27
  for (const tag of event.tags) {
28
28
  if (tag[0] === "r" && tag[1] && (tag[2] === "write" || tag[2] === undefined)) {
29
29
  const url = safeRelayUrl(tag[1]);
30
- if (url)
31
- outboxes.add(url);
30
+ if (url && !outboxes.includes(url))
31
+ outboxes.push(url);
32
32
  }
33
33
  }
34
- event[MailboxesOutboxesSymbol] = outboxes;
35
- }
36
- return event[MailboxesOutboxesSymbol];
34
+ return outboxes;
35
+ });
37
36
  }
@@ -10,7 +10,7 @@ export declare function parseCoordinate(a: string, requireD: true, silent: false
10
10
  export declare function parseCoordinate(a: string, requireD: true, silent: true): AddressPointer | null;
11
11
  export declare function parseCoordinate(a: string, requireD: false, silent: true): AddressPointerWithoutD | null;
12
12
  export declare function getPubkeyFromDecodeResult(result?: DecodeResult): string | undefined;
13
- export declare function encodeDecodeResult(result: DecodeResult): "" | `nsec1${string}` | `npub1${string}` | `note1${string}` | `nprofile1${string}` | `nevent1${string}` | `naddr1${string}` | `nrelay1${string}`;
13
+ export declare function encodeDecodeResult(result: DecodeResult): "" | `nprofile1${string}` | `nevent1${string}` | `naddr1${string}` | `nsec1${string}` | `npub1${string}` | `note1${string}`;
14
14
  export declare function getEventPointerFromTag(tag: string[]): EventPointer;
15
15
  export declare function getAddressPointerFromTag(tag: string[]): AddressPointer;
16
16
  export declare function getProfilePointerFromTag(tag: string[]): ProfilePointer;
@@ -1,4 +1,4 @@
1
- import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nrelayEncode, nsecEncode, } from "nostr-tools/nip19";
1
+ import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, } from "nostr-tools/nip19";
2
2
  import { getPublicKey } from "nostr-tools";
3
3
  import { safeRelayUrls } from "./relays.js";
4
4
  export function parseCoordinate(a, requireD = false, silent = true) {
@@ -53,8 +53,6 @@ export function encodeDecodeResult(result) {
53
53
  return nprofileEncode(result.data);
54
54
  case "nevent":
55
55
  return neventEncode(result.data);
56
- case "nrelay":
57
- return nrelayEncode(result.data);
58
56
  case "nsec":
59
57
  return nsecEncode(result.data);
60
58
  case "npub":
@@ -1,10 +1,5 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
2
  export declare const ProfileContentSymbol: unique symbol;
3
- declare module "nostr-tools" {
4
- interface Event {
5
- [ProfileContentSymbol]?: ProfileContent | Error;
6
- }
7
- }
8
3
  export type ProfileContent = {
9
4
  name?: string;
10
5
  display_name?: string;
@@ -21,5 +16,5 @@ export type ProfileContent = {
21
16
  };
22
17
  /** Returns the parsed profile content for a kind 0 event */
23
18
  export declare function getProfileContent(event: NostrEvent): ProfileContent;
24
- export declare function getProfileContent(event: NostrEvent, quite: false): ProfileContent;
25
- export declare function getProfileContent(event: NostrEvent, quite: true): ProfileContent | Error;
19
+ /** Checks if the content of the kind 0 event is valid JSON */
20
+ export declare function isValidProfile(profile?: NostrEvent): boolean;
@@ -1,28 +1,31 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { getOrComputeCachedValue } from "./cache.js";
1
3
  export const ProfileContentSymbol = Symbol.for("profile-content");
2
- export function getProfileContent(event, quite = false) {
3
- let cached = event[ProfileContentSymbol];
4
- if (!cached) {
5
- try {
6
- const profile = JSON.parse(event.content);
7
- // ensure nip05 is a string
8
- if (profile.nip05 && typeof profile.nip05 !== "string")
9
- profile.nip05 = String(profile.nip05);
10
- cached = event[ProfileContentSymbol] = profile;
4
+ /** Returns the parsed profile content for a kind 0 event */
5
+ export function getProfileContent(event) {
6
+ return getOrComputeCachedValue(event, ProfileContentSymbol, () => {
7
+ const profile = JSON.parse(event.content);
8
+ // ensure nip05 is a string
9
+ if (profile.nip05 && typeof profile.nip05 !== "string")
10
+ profile.nip05 = String(profile.nip05);
11
+ // add missing protocol to website
12
+ if (profile.website?.startsWith("http") === false) {
13
+ profile.website = "https://" + profile.website;
11
14
  }
12
- catch (e) {
13
- if (e instanceof Error)
14
- cached = event[ProfileContentSymbol] = e;
15
- }
16
- }
17
- if (cached === undefined) {
18
- throw new Error("Failed to parse profile");
15
+ return profile;
16
+ });
17
+ }
18
+ /** Checks if the content of the kind 0 event is valid JSON */
19
+ export function isValidProfile(profile) {
20
+ if (!profile)
21
+ return false;
22
+ if (profile.kind !== kinds.Metadata)
23
+ return false;
24
+ try {
25
+ getProfileContent(profile);
26
+ return true;
19
27
  }
20
- else if (cached instanceof Error) {
21
- if (!quite)
22
- throw cached;
23
- else
24
- return cached;
28
+ catch (error) {
29
+ return false;
25
30
  }
26
- else
27
- return cached;
28
31
  }
@@ -1,4 +1,4 @@
1
- import { NostrEvent } from "nostr-tools";
1
+ import { EventTemplate, NostrEvent } from "nostr-tools";
2
2
  import { AddressPointer, EventPointer } from "nostr-tools/nip19";
3
3
  export type ThreadReferences = {
4
4
  root?: {
@@ -29,7 +29,7 @@ declare module "nostr-tools" {
29
29
  }
30
30
  }
31
31
  /** Parses NIP-10 tags and handles legacy behavior */
32
- export declare function interpretThreadTags(event: NostrEvent): {
32
+ export declare function interpretThreadTags(event: NostrEvent | EventTemplate): {
33
33
  root?: {
34
34
  e: string[];
35
35
  a: undefined;
@@ -39,7 +39,7 @@ export declare function interpretThreadTags(event: NostrEvent): {
39
39
  } | {
40
40
  e: string[];
41
41
  a: string[];
42
- } | undefined;
42
+ };
43
43
  reply?: {
44
44
  e: string[];
45
45
  a: undefined;
@@ -49,7 +49,7 @@ export declare function interpretThreadTags(event: NostrEvent): {
49
49
  } | {
50
50
  e: string[];
51
51
  a: string[];
52
- } | undefined;
52
+ };
53
53
  };
54
54
  /** Returns the parsed NIP-10 tags for an event */
55
- export declare function getNip10References(event: NostrEvent): ThreadReferences;
55
+ export declare function getNip10References(event: NostrEvent | EventTemplate): ThreadReferences;
@@ -1,4 +1,5 @@
1
1
  import { getAddressPointerFromTag, getEventPointerFromTag } from "./pointers.js";
2
+ import { getOrComputeCachedValue } from "./cache.js";
2
3
  export const Nip10ThreadRefsSymbol = Symbol.for("nip10-thread-refs");
3
4
  /** Parses NIP-10 tags and handles legacy behavior */
4
5
  export function interpretThreadTags(event) {
@@ -42,20 +43,31 @@ export function interpretThreadTags(event) {
42
43
  }
43
44
  /** Returns the parsed NIP-10 tags for an event */
44
45
  export function getNip10References(event) {
45
- let refs = event[Nip10ThreadRefsSymbol];
46
- if (!refs) {
47
- const e = event;
48
- const tags = interpretThreadTags(e);
49
- refs = event[Nip10ThreadRefsSymbol] = {
50
- root: tags.root && {
51
- e: tags.root.e && getEventPointerFromTag(tags.root.e),
52
- a: tags.root.a && getAddressPointerFromTag(tags.root.a),
53
- },
54
- reply: tags.reply && {
55
- e: tags.reply.e && getEventPointerFromTag(tags.reply.e),
56
- a: tags.reply.a && getAddressPointerFromTag(tags.reply.a),
57
- },
46
+ return getOrComputeCachedValue(event, Nip10ThreadRefsSymbol, () => {
47
+ const tags = interpretThreadTags(event);
48
+ let root;
49
+ if (tags.root) {
50
+ try {
51
+ root = {
52
+ e: tags.root.e && getEventPointerFromTag(tags.root.e),
53
+ a: tags.root.a && getAddressPointerFromTag(tags.root.a),
54
+ };
55
+ }
56
+ catch (error) { }
57
+ }
58
+ let reply;
59
+ if (tags.reply) {
60
+ try {
61
+ reply = {
62
+ e: tags.reply.e && getEventPointerFromTag(tags.reply.e),
63
+ a: tags.reply.a && getAddressPointerFromTag(tags.reply.a),
64
+ };
65
+ }
66
+ catch (error) { }
67
+ }
68
+ return {
69
+ root,
70
+ reply,
58
71
  };
59
- }
60
- return refs;
72
+ });
61
73
  }
@@ -0,0 +1,14 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export declare const ZapRequestSymbol: unique symbol;
3
+ export declare const ZapFromSymbol: unique symbol;
4
+ export declare const ZapInvoiceSymbol: unique symbol;
5
+ export declare const ZapEventPointerSymbol: unique symbol;
6
+ export declare const ZapAddressPointerSymbol: unique symbol;
7
+ export declare function getZapSender(zap: NostrEvent): string;
8
+ export declare function getZapRecipient(zap: NostrEvent): string;
9
+ export declare function getZapPayment(zap: NostrEvent): import("./bolt11.js").ParsedInvoice | undefined;
10
+ export declare function getZapAddressPointer(zap: NostrEvent): import("nostr-tools/nip19").AddressPointer | null;
11
+ export declare function getZapEventPointer(zap: NostrEvent): import("nostr-tools/nip19").EventPointer | null;
12
+ export declare function getZapPreimage(zap: NostrEvent): string | undefined;
13
+ export declare function getZapRequest(zap: NostrEvent): import("nostr-tools").Event;
14
+ export declare function isValidZap(zap?: NostrEvent): boolean;
@@ -0,0 +1,66 @@
1
+ import { kinds, nip57 } from "nostr-tools";
2
+ import { getOrComputeCachedValue } from "./cache.js";
3
+ import { getTagValue } from "./event.js";
4
+ import { isATag, isETag } from "./tags.js";
5
+ import { getAddressPointerFromTag, getEventPointerFromTag } from "./pointers.js";
6
+ import { parseBolt11 } from "./bolt11.js";
7
+ export const ZapRequestSymbol = Symbol.for("zap-request");
8
+ export const ZapFromSymbol = Symbol.for("zap-from");
9
+ export const ZapInvoiceSymbol = Symbol.for("zap-bolt11");
10
+ export const ZapEventPointerSymbol = Symbol.for("zap-event-pointer");
11
+ export const ZapAddressPointerSymbol = Symbol.for("zap-address-pointer");
12
+ export function getZapSender(zap) {
13
+ return getTagValue(zap, "P") || getZapRequest(zap).pubkey;
14
+ }
15
+ export function getZapRecipient(zap) {
16
+ const recipient = getTagValue(zap, "p");
17
+ if (!recipient)
18
+ throw new Error("Missing recipient");
19
+ return recipient;
20
+ }
21
+ export function getZapPayment(zap) {
22
+ return getOrComputeCachedValue(zap, ZapInvoiceSymbol, () => {
23
+ const bolt11 = getTagValue(zap, "bolt11");
24
+ return bolt11 ? parseBolt11(bolt11) : undefined;
25
+ });
26
+ }
27
+ export function getZapAddressPointer(zap) {
28
+ return getOrComputeCachedValue(zap, ZapAddressPointerSymbol, () => {
29
+ const a = zap.tags.find(isATag);
30
+ return a ? getAddressPointerFromTag(a) : null;
31
+ });
32
+ }
33
+ export function getZapEventPointer(zap) {
34
+ return getOrComputeCachedValue(zap, ZapEventPointerSymbol, () => {
35
+ const e = zap.tags.find(isETag);
36
+ return e ? getEventPointerFromTag(e) : null;
37
+ });
38
+ }
39
+ export function getZapPreimage(zap) {
40
+ return getTagValue(zap, "preimage");
41
+ }
42
+ export function getZapRequest(zap) {
43
+ return getOrComputeCachedValue(zap, ZapRequestSymbol, () => {
44
+ const description = getTagValue(zap, "description");
45
+ if (!description)
46
+ throw new Error("Missing description tag");
47
+ const error = nip57.validateZapRequest(description);
48
+ if (error)
49
+ throw new Error(error);
50
+ return JSON.parse(description);
51
+ });
52
+ }
53
+ export function isValidZap(zap) {
54
+ if (!zap)
55
+ return false;
56
+ if (zap.kind !== kinds.Zap)
57
+ return false;
58
+ try {
59
+ getZapRequest(zap);
60
+ getZapRecipient(zap);
61
+ return true;
62
+ }
63
+ catch (error) {
64
+ return false;
65
+ }
66
+ }
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from "./event-store/index.js";
2
2
  export * from "./query-store/index.js";
3
3
  export * as Helpers from "./helpers/index.js";
4
4
  export * as Queries from "./queries/index.js";
5
+ export * from "./logger.js";
package/dist/index.js CHANGED
@@ -2,3 +2,4 @@ export * from "./event-store/index.js";
2
2
  export * from "./query-store/index.js";
3
3
  export * as Helpers from "./helpers/index.js";
4
4
  export * as Queries from "./queries/index.js";
5
+ export * from "./logger.js";
@@ -1,2 +1,2 @@
1
- import Observable from "zen-observable";
1
+ import { Observable } from "rxjs";
2
2
  export declare function getValue<T>(observable: Observable<T>): T | Promise<T>;
@@ -1,7 +1,9 @@
1
- import { isStateful } from "./stateful.js";
1
+ import { BehaviorSubject } from "rxjs";
2
2
  export function getValue(observable) {
3
- if (isStateful(observable) && observable.value !== undefined)
3
+ if (observable instanceof BehaviorSubject)
4
4
  return observable.value;
5
+ if (Reflect.has(observable, "value"))
6
+ return Reflect.get(observable, "value");
5
7
  return new Promise((res) => {
6
8
  const sub = observable.subscribe((v) => {
7
9
  res(v);
@@ -1,3 +1,2 @@
1
- export * from "./stateful.js";
2
- export * from "./throttle.js";
3
1
  export * from "./getValue.js";
2
+ export * from "./share-latest-value.js";
@@ -1,3 +1,2 @@
1
- export * from "./stateful.js";
2
- export * from "./throttle.js";
3
1
  export * from "./getValue.js";
2
+ export * from "./share-latest-value.js";
@@ -0,0 +1,8 @@
1
+ import { Observable, ShareConfig } from "rxjs";
2
+ /**
3
+ * Creates an operator that adds a 'value' property and multiplexes the source
4
+ * @param config Optional ShareConfig for customizing sharing behavior
5
+ */
6
+ export declare function shareLatestValue<T>(config?: ShareConfig<T>): (source: Observable<T>) => Observable<T> & {
7
+ value: T | undefined;
8
+ };
@@ -0,0 +1,21 @@
1
+ import { share } from "rxjs";
2
+ import { tap } from "rxjs/operators";
3
+ /**
4
+ * Creates an operator that adds a 'value' property and multiplexes the source
5
+ * @param config Optional ShareConfig for customizing sharing behavior
6
+ */
7
+ export function shareLatestValue(config = {}) {
8
+ return (source) => {
9
+ // Create storage for latest value
10
+ let latestValue = undefined;
11
+ // Create shared source with value tracking
12
+ const shared$ = source.pipe(tap((value) => {
13
+ latestValue = value;
14
+ }), share(config));
15
+ // Add value property
16
+ Object.defineProperty(shared$, "value", {
17
+ get: () => latestValue,
18
+ });
19
+ return shared$;
20
+ };
21
+ }
@@ -3,3 +3,4 @@ export * from "./profile.js";
3
3
  export * from "./mailboxes.js";
4
4
  export * from "./reactions.js";
5
5
  export * from "./thread.js";
6
+ export * from "./zaps.js";
@@ -3,3 +3,4 @@ export * from "./profile.js";
3
3
  export * from "./mailboxes.js";
4
4
  export * from "./reactions.js";
5
5
  export * from "./thread.js";
6
+ export * from "./zaps.js";
@@ -1,5 +1,5 @@
1
1
  import { Query } from "../query-store/index.js";
2
2
  export declare function MailboxesQuery(pubkey: string): Query<{
3
- inboxes: Set<string>;
4
- outboxes: Set<string>;
3
+ inboxes: string[];
4
+ outboxes: string[];
5
5
  } | undefined>;
@@ -1,11 +1,12 @@
1
1
  import { kinds } from "nostr-tools";
2
+ import { map } from "rxjs/operators";
2
3
  import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
3
4
  export function MailboxesQuery(pubkey) {
4
5
  return {
5
6
  key: pubkey,
6
- run: (events) => events.replaceable(kinds.RelayList, pubkey).map((event) => event && {
7
+ run: (events) => events.replaceable(kinds.RelayList, pubkey).pipe(map((event) => event && {
7
8
  inboxes: getInboxes(event),
8
9
  outboxes: getOutboxes(event),
9
- }),
10
+ })),
10
11
  };
11
12
  }
@@ -1,10 +1,11 @@
1
1
  import { kinds } from "nostr-tools";
2
- import { getProfileContent } from "../helpers/profile.js";
2
+ import { filter, map } from "rxjs/operators";
3
+ import { getProfileContent, isValidProfile } from "../helpers/profile.js";
3
4
  export function ProfileQuery(pubkey) {
4
5
  return {
5
6
  key: pubkey,
6
7
  run: (events) => {
7
- return events.replaceable(kinds.Metadata, pubkey).map((event) => event && getProfileContent(event));
8
+ return events.replaceable(kinds.Metadata, pubkey).pipe(filter(isValidProfile), map((event) => event && getProfileContent(event)));
8
9
  },
9
10
  };
10
11
  }
@@ -1,4 +1,5 @@
1
1
  import { kinds } from "nostr-tools";
2
+ import { map } from "rxjs/operators";
2
3
  import { getNip10References } from "../helpers/threading.js";
3
4
  import { getCoordinateFromAddressPointer, isAddressPointer } from "../helpers/pointers.js";
4
5
  import { getEventUID } from "../helpers/event.js";
@@ -31,7 +32,7 @@ export function ThreadQuery(root, opts) {
31
32
  }
32
33
  return {
33
34
  key: `${rootUID}-${kinds.join(",")}`,
34
- run: (events) => events.stream([rootFilter, replyFilter]).map((event) => {
35
+ run: (events) => events.stream([rootFilter, replyFilter]).pipe(map((event) => {
35
36
  if (!items.has(getEventUID(event))) {
36
37
  const refs = getNip10References(event);
37
38
  const replies = parentReferences.get(getEventUID(event)) || new Set();
@@ -60,6 +61,6 @@ export function ThreadQuery(root, opts) {
60
61
  items.set(getEventUID(event), item);
61
62
  }
62
63
  return { root: items.get(rootUID), all: items };
63
- }),
64
+ })),
64
65
  };
65
66
  }
@@ -0,0 +1,4 @@
1
+ import { AddressPointer, EventPointer } from "nostr-tools/nip19";
2
+ import { NostrEvent } from "nostr-tools";
3
+ import { Query } from "../query-store/index.js";
4
+ export declare function EventZapsQuery(id: string | EventPointer | AddressPointer): Query<NostrEvent[]>;
@@ -0,0 +1,20 @@
1
+ import { map } from "rxjs";
2
+ import { kinds } from "nostr-tools";
3
+ import { getCoordinateFromAddressPointer, isAddressPointer } from "../helpers/pointers.js";
4
+ import { isValidZap } from "../helpers/zap.js";
5
+ export function EventZapsQuery(id) {
6
+ return {
7
+ key: JSON.stringify(id),
8
+ run: (events) => {
9
+ if (isAddressPointer(id)) {
10
+ return events
11
+ .timeline([{ kinds: [kinds.Zap], "#a": [getCoordinateFromAddressPointer(id)] }])
12
+ .pipe(map((events) => events.filter(isValidZap)));
13
+ }
14
+ else {
15
+ id = typeof id === "string" ? id : id.id;
16
+ return events.timeline([{ kinds: [kinds.Zap], "#e": [id] }]).pipe(map((events) => events.filter(isValidZap)));
17
+ }
18
+ },
19
+ };
20
+ }
@@ -1,7 +1,6 @@
1
- import Observable from "zen-observable";
1
+ import { BehaviorSubject, Observable } from "rxjs";
2
2
  import { Filter, NostrEvent } from "nostr-tools";
3
3
  import { EventStore } from "../event-store/event-store.js";
4
- import { StatefulObservable } from "../observable/stateful.js";
5
4
  import { LRU } from "../helpers/lru.js";
6
5
  import * as Queries from "../queries/index.js";
7
6
  import { AddressPointer, EventPointer } from "nostr-tools/nip19";
@@ -14,7 +13,7 @@ export declare class QueryStore {
14
13
  static Queries: typeof Queries;
15
14
  store: EventStore;
16
15
  constructor(store: EventStore);
17
- queries: LRU<StatefulObservable<any>>;
16
+ queries: LRU<Observable<any> | BehaviorSubject<any>>;
18
17
  /** Creates a cached query */
19
18
  runQuery<T extends unknown, Args extends Array<any>>(queryConstructor: (...args: Args) => {
20
19
  key: string;
@@ -40,8 +39,8 @@ export declare class QueryStore {
40
39
  reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
41
40
  /** Returns the parsed relay list (10002) for the pubkey */
42
41
  mailboxes(pubkey: string): Observable<{
43
- inboxes: Set<string>;
44
- outboxes: Set<string>;
42
+ inboxes: string[];
43
+ outboxes: string[];
45
44
  } | undefined>;
46
45
  thread(root: string | EventPointer | AddressPointer): Observable<Queries.Thread>;
47
46
  }
@@ -1,6 +1,6 @@
1
- import { stateful } from "../observable/stateful.js";
2
1
  import { LRU } from "../helpers/lru.js";
3
2
  import * as Queries from "../queries/index.js";
3
+ import { shareLatestValue } from "../observable/share-latest-value.js";
4
4
  export class QueryStore {
5
5
  static Queries = Queries;
6
6
  store;
@@ -14,7 +14,7 @@ export class QueryStore {
14
14
  const query = queryConstructor(...args);
15
15
  const key = `${queryConstructor.name}|${query.key}`;
16
16
  if (!this.queries.has(key)) {
17
- const observable = stateful(query.run(this.store, this));
17
+ const observable = query.run(this.store, this).pipe(shareLatestValue());
18
18
  this.queries.set(key, observable);
19
19
  return observable;
20
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,19 +54,18 @@
54
54
  "dependencies": {
55
55
  "debug": "^4.3.7",
56
56
  "json-stringify-deterministic": "^1.0.12",
57
+ "light-bolt11-decoder": "^3.2.0",
57
58
  "nanoid": "^5.0.7",
58
- "nostr-tools": "^2.7.2",
59
- "zen-push": "^0.3.1",
60
- "zen-observable": "^0.10.0"
59
+ "nostr-tools": "^2.10.1",
60
+ "rxjs": "^7.8.1"
61
61
  },
62
62
  "devDependencies": {
63
- "@types/zen-push": "^0.1.4",
64
63
  "@jest/globals": "^29.7.0",
65
64
  "@types/debug": "^4.1.12",
66
65
  "@types/jest": "^29.5.13",
67
- "@types/zen-observable": "^0.8.7",
68
66
  "jest": "^29.7.0",
69
- "jest-extended": "^4.0.2"
67
+ "jest-extended": "^4.0.2",
68
+ "typescript": "^5.6.3"
70
69
  },
71
70
  "jest": {
72
71
  "roots": [
@@ -76,6 +75,10 @@
76
75
  "jest-extended/all"
77
76
  ]
78
77
  },
78
+ "funding": {
79
+ "type": "lightning",
80
+ "url": "lightning:nostrudel@geyser.fund"
81
+ },
79
82
  "scripts": {
80
83
  "build": "tsc",
81
84
  "watch:build": "tsc --watch > /dev/null",
@@ -1,10 +0,0 @@
1
- import Observable from "zen-observable";
2
- export type StatefulObservable<T> = Observable<T> & {
3
- _stateful?: true;
4
- value?: T;
5
- error?: Error;
6
- complete?: boolean;
7
- };
8
- /** Wraps an {@link Observable} and makes it stateful */
9
- export declare function stateful<T extends unknown>(observable: Observable<T>, cleanup?: boolean): StatefulObservable<T>;
10
- export declare function isStateful<T extends unknown>(observable: Observable<T> | StatefulObservable<T>): observable is StatefulObservable<T>;
@@ -1,60 +0,0 @@
1
- import Observable from "zen-observable";
2
- /** Wraps an {@link Observable} and makes it stateful */
3
- export function stateful(observable, cleanup = false) {
4
- let subscription = undefined;
5
- let observers = [];
6
- const self = new Observable((observer) => {
7
- // add observer to list
8
- observers.push(observer);
9
- // pass any cached values
10
- if (self.value)
11
- observer.next(self.value);
12
- if (self.error)
13
- observer.error(self.error);
14
- if (self.complete)
15
- observer.complete();
16
- // subscribe if not already
17
- if (!subscription) {
18
- subscription = observable.subscribe({
19
- next: (v) => {
20
- self.value = v;
21
- for (const observer of observers)
22
- observer.next(v);
23
- },
24
- error: (err) => {
25
- self.error = err;
26
- for (const observer of observers)
27
- observer.error(err);
28
- },
29
- complete: () => {
30
- self.complete = true;
31
- for (const observer of observers)
32
- observer.complete();
33
- },
34
- });
35
- }
36
- return () => {
37
- let i = observers.indexOf(observer);
38
- if (i !== -1) {
39
- // remove observer from list
40
- observers.splice(i, 1);
41
- if (subscription && observers.length === 0) {
42
- subscription.unsubscribe();
43
- subscription = undefined;
44
- // reset cached values
45
- if (cleanup) {
46
- delete self.value;
47
- delete self.error;
48
- delete self.complete;
49
- }
50
- }
51
- }
52
- };
53
- });
54
- self._stateful = true;
55
- return self;
56
- }
57
- export function isStateful(observable) {
58
- // @ts-expect-error
59
- return observable._stateful;
60
- }
@@ -1,3 +0,0 @@
1
- import Observable from "zen-observable";
2
- /** Throttles an {@link Observable} */
3
- export declare function throttle<T>(source: Observable<T>, interval: number): Observable<T>;
@@ -1,23 +0,0 @@
1
- import Observable from "zen-observable";
2
- /** Throttles an {@link Observable} */
3
- export function throttle(source, interval) {
4
- return new Observable((observer) => {
5
- let lastEmissionTime = 0;
6
- let subscription = source.subscribe({
7
- next(value) {
8
- const currentTime = Date.now();
9
- if (currentTime - lastEmissionTime >= interval) {
10
- lastEmissionTime = currentTime;
11
- observer.next(value);
12
- }
13
- },
14
- error(err) {
15
- observer.error(err);
16
- },
17
- complete() {
18
- observer.complete();
19
- },
20
- });
21
- return () => subscription.unsubscribe();
22
- });
23
- }