applesauce-core 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 (194) hide show
  1. package/README.md +7 -13
  2. package/dist/event-store/{database.d.ts → event-set.d.ts} +35 -20
  3. package/dist/event-store/{database.js → event-set.js} +91 -60
  4. package/dist/event-store/event-store.d.ts +64 -25
  5. package/dist/event-store/event-store.js +163 -206
  6. package/dist/event-store/index.d.ts +1 -1
  7. package/dist/event-store/index.js +1 -1
  8. package/dist/event-store/interface.d.ts +71 -13
  9. package/dist/helpers/app-handlers.d.ts +23 -0
  10. package/dist/helpers/app-handlers.js +68 -0
  11. package/dist/helpers/article.d.ts +9 -0
  12. package/dist/helpers/article.js +21 -0
  13. package/dist/helpers/blossom.d.ts +2 -0
  14. package/dist/helpers/blossom.js +18 -0
  15. package/dist/helpers/bolt11.d.ts +1 -0
  16. package/dist/helpers/bolt11.js +2 -0
  17. package/dist/helpers/bookmarks.js +1 -2
  18. package/dist/helpers/emoji.d.ts +10 -2
  19. package/dist/helpers/emoji.js +21 -3
  20. package/dist/helpers/encrypted-content-cache.d.ts +15 -0
  21. package/dist/helpers/encrypted-content-cache.js +125 -0
  22. package/dist/helpers/encrypted-content.d.ts +48 -0
  23. package/dist/helpers/encrypted-content.js +65 -0
  24. package/dist/helpers/encryption.d.ts +5 -0
  25. package/dist/helpers/encryption.js +10 -0
  26. package/dist/helpers/event.d.ts +4 -1
  27. package/dist/helpers/event.js +13 -3
  28. package/dist/helpers/expiration.d.ts +6 -0
  29. package/dist/helpers/expiration.js +16 -0
  30. package/dist/helpers/filter.d.ts +1 -3
  31. package/dist/helpers/filter.js +1 -3
  32. package/dist/helpers/gift-wraps.d.ts +17 -5
  33. package/dist/helpers/gift-wraps.js +65 -27
  34. package/dist/helpers/groups.d.ts +5 -0
  35. package/dist/helpers/groups.js +12 -2
  36. package/dist/helpers/hidden-content.d.ts +27 -32
  37. package/dist/helpers/hidden-content.js +35 -65
  38. package/dist/helpers/hidden-tags.d.ts +23 -4
  39. package/dist/helpers/hidden-tags.js +39 -4
  40. package/dist/helpers/index.d.ts +11 -1
  41. package/dist/helpers/index.js +11 -1
  42. package/dist/helpers/legacy-messages.d.ts +21 -0
  43. package/dist/helpers/legacy-messages.js +39 -0
  44. package/dist/helpers/lists.d.ts +1 -1
  45. package/dist/helpers/lists.js +2 -2
  46. package/dist/helpers/messages.d.ts +11 -0
  47. package/dist/helpers/messages.js +19 -0
  48. package/dist/helpers/mutes.js +1 -1
  49. package/dist/helpers/pointers.d.ts +33 -9
  50. package/dist/helpers/pointers.js +80 -44
  51. package/dist/helpers/profile.d.ts +10 -2
  52. package/dist/helpers/profile.js +33 -4
  53. package/dist/helpers/reactions.d.ts +8 -0
  54. package/dist/helpers/reactions.js +56 -0
  55. package/dist/helpers/reports.d.ts +28 -0
  56. package/dist/helpers/reports.js +38 -0
  57. package/dist/helpers/share.d.ts +10 -1
  58. package/dist/helpers/share.js +22 -8
  59. package/dist/helpers/url.d.ts +4 -0
  60. package/dist/helpers/url.js +20 -0
  61. package/dist/helpers/user-status.js +2 -1
  62. package/dist/helpers/wrapped-messages.d.ts +23 -0
  63. package/dist/helpers/wrapped-messages.js +38 -0
  64. package/dist/helpers/zap.d.ts +8 -5
  65. package/dist/helpers/zap.js +11 -6
  66. package/dist/index.d.ts +2 -2
  67. package/dist/index.js +2 -2
  68. package/dist/models/blossom.d.ts +3 -0
  69. package/dist/models/blossom.js +8 -0
  70. package/dist/models/bookmarks.d.ts +8 -0
  71. package/dist/{queries → models}/bookmarks.js +9 -9
  72. package/dist/models/channels.d.ts +11 -0
  73. package/dist/{queries → models}/channels.js +9 -9
  74. package/dist/models/comments.d.ts +4 -0
  75. package/dist/models/comments.js +11 -0
  76. package/dist/models/common.d.ts +16 -0
  77. package/dist/models/common.js +176 -0
  78. package/dist/models/contacts.d.ts +8 -0
  79. package/dist/{queries → models}/contacts.js +10 -10
  80. package/dist/models/encrypted-content.d.ts +4 -0
  81. package/dist/models/encrypted-content.js +11 -0
  82. package/dist/models/gift-wrap.d.ts +7 -0
  83. package/dist/models/gift-wrap.js +20 -0
  84. package/dist/{queries → models}/index.d.ts +6 -2
  85. package/dist/{queries → models}/index.js +6 -2
  86. package/dist/models/legacy-messages.d.ts +8 -0
  87. package/dist/models/legacy-messages.js +29 -0
  88. package/dist/models/mailboxes.d.ts +6 -0
  89. package/dist/{queries → models}/mailboxes.js +2 -2
  90. package/dist/models/mutes.d.ts +8 -0
  91. package/dist/{queries → models}/mutes.js +9 -9
  92. package/dist/models/pins.d.ts +4 -0
  93. package/dist/{queries → models}/pins.js +3 -3
  94. package/dist/models/profile.d.ts +4 -0
  95. package/dist/models/profile.js +14 -0
  96. package/dist/models/reactions.d.ts +4 -0
  97. package/dist/{queries → models}/reactions.js +2 -2
  98. package/dist/models/relays.d.ts +27 -0
  99. package/dist/{queries → models}/relays.js +13 -13
  100. package/dist/{queries → models}/thread.d.ts +6 -5
  101. package/dist/{queries → models}/thread.js +4 -3
  102. package/dist/models/user-status.d.ts +11 -0
  103. package/dist/{queries → models}/user-status.js +5 -5
  104. package/dist/models/wrapped-messages.d.ts +25 -0
  105. package/dist/models/wrapped-messages.js +61 -0
  106. package/dist/models/zaps.d.ts +9 -0
  107. package/dist/{queries → models}/zaps.js +11 -3
  108. package/dist/observable/claim-events.d.ts +3 -3
  109. package/dist/observable/claim-events.js +4 -4
  110. package/dist/observable/claim-latest.d.ts +3 -3
  111. package/dist/observable/claim-latest.js +4 -4
  112. package/dist/observable/index.d.ts +5 -1
  113. package/dist/observable/index.js +6 -1
  114. package/dist/observable/map-events-timeline.d.ts +7 -0
  115. package/dist/observable/map-events-timeline.js +9 -0
  116. package/dist/observable/map-events-to-store.d.ts +5 -0
  117. package/dist/observable/map-events-to-store.js +12 -0
  118. package/dist/observable/simple-timeout.d.ts +1 -0
  119. package/dist/observable/simple-timeout.js +1 -0
  120. package/dist/observable/watch-event-updates.d.ts +7 -0
  121. package/dist/observable/watch-event-updates.js +25 -0
  122. package/package.json +11 -16
  123. package/dist/__tests__/fixtures.d.ts +0 -8
  124. package/dist/__tests__/fixtures.js +0 -20
  125. package/dist/event-store/__tests__/event-store.test.d.ts +0 -1
  126. package/dist/event-store/__tests__/event-store.test.js +0 -354
  127. package/dist/helpers/__tests__/blossom.test.d.ts +0 -1
  128. package/dist/helpers/__tests__/blossom.test.js +0 -13
  129. package/dist/helpers/__tests__/bookmarks.test.d.ts +0 -1
  130. package/dist/helpers/__tests__/bookmarks.test.js +0 -88
  131. package/dist/helpers/__tests__/comment.test.d.ts +0 -1
  132. package/dist/helpers/__tests__/comment.test.js +0 -249
  133. package/dist/helpers/__tests__/contacts.test.d.ts +0 -1
  134. package/dist/helpers/__tests__/contacts.test.js +0 -34
  135. package/dist/helpers/__tests__/emoji.test.d.ts +0 -1
  136. package/dist/helpers/__tests__/emoji.test.js +0 -15
  137. package/dist/helpers/__tests__/event.test.d.ts +0 -1
  138. package/dist/helpers/__tests__/event.test.js +0 -36
  139. package/dist/helpers/__tests__/events.test.d.ts +0 -1
  140. package/dist/helpers/__tests__/events.test.js +0 -32
  141. package/dist/helpers/__tests__/file-metadata.test.d.ts +0 -1
  142. package/dist/helpers/__tests__/file-metadata.test.js +0 -103
  143. package/dist/helpers/__tests__/hidden-tags.test.d.ts +0 -1
  144. package/dist/helpers/__tests__/hidden-tags.test.js +0 -29
  145. package/dist/helpers/__tests__/mailboxes.test.d.ts +0 -1
  146. package/dist/helpers/__tests__/mailboxes.test.js +0 -81
  147. package/dist/helpers/__tests__/mutes.test.d.ts +0 -1
  148. package/dist/helpers/__tests__/mutes.test.js +0 -55
  149. package/dist/helpers/__tests__/nip-19.test.d.ts +0 -1
  150. package/dist/helpers/__tests__/nip-19.test.js +0 -42
  151. package/dist/helpers/__tests__/relays.test.d.ts +0 -1
  152. package/dist/helpers/__tests__/relays.test.js +0 -21
  153. package/dist/helpers/__tests__/tags.test.d.ts +0 -1
  154. package/dist/helpers/__tests__/tags.test.js +0 -24
  155. package/dist/helpers/__tests__/threading.test.d.ts +0 -1
  156. package/dist/helpers/__tests__/threading.test.js +0 -41
  157. package/dist/helpers/direct-messages.d.ts +0 -4
  158. package/dist/helpers/direct-messages.js +0 -5
  159. package/dist/helpers/nip-19.d.ts +0 -18
  160. package/dist/helpers/nip-19.js +0 -56
  161. package/dist/observable/__tests__/claim-events.test.d.ts +0 -1
  162. package/dist/observable/__tests__/claim-events.test.js +0 -23
  163. package/dist/observable/__tests__/claim-latest.test.d.ts +0 -1
  164. package/dist/observable/__tests__/claim-latest.test.js +0 -37
  165. package/dist/observable/__tests__/listen-latest-updates.test.d.ts +0 -1
  166. package/dist/observable/__tests__/listen-latest-updates.test.js +0 -55
  167. package/dist/observable/__tests__/simple-timeout.test.d.ts +0 -1
  168. package/dist/observable/__tests__/simple-timeout.test.js +0 -34
  169. package/dist/observable/listen-latest-updates.d.ts +0 -5
  170. package/dist/observable/listen-latest-updates.js +0 -12
  171. package/dist/queries/blossom.d.ts +0 -2
  172. package/dist/queries/blossom.js +0 -5
  173. package/dist/queries/bookmarks.d.ts +0 -8
  174. package/dist/queries/channels.d.ts +0 -11
  175. package/dist/queries/comments.d.ts +0 -4
  176. package/dist/queries/comments.js +0 -11
  177. package/dist/queries/contacts.d.ts +0 -8
  178. package/dist/queries/mailboxes.d.ts +0 -6
  179. package/dist/queries/mutes.d.ts +0 -8
  180. package/dist/queries/pins.d.ts +0 -4
  181. package/dist/queries/profile.d.ts +0 -4
  182. package/dist/queries/profile.js +0 -7
  183. package/dist/queries/reactions.d.ts +0 -4
  184. package/dist/queries/relays.d.ts +0 -27
  185. package/dist/queries/simple.d.ts +0 -16
  186. package/dist/queries/simple.js +0 -21
  187. package/dist/queries/user-status.d.ts +0 -11
  188. package/dist/queries/zaps.d.ts +0 -5
  189. package/dist/query-store/__tests__/query-store.test.d.ts +0 -1
  190. package/dist/query-store/__tests__/query-store.test.js +0 -63
  191. package/dist/query-store/index.d.ts +0 -1
  192. package/dist/query-store/index.js +0 -1
  193. package/dist/query-store/query-store.d.ts +0 -54
  194. package/dist/query-store/query-store.js +0 -102
package/README.md CHANGED
@@ -1,13 +1,12 @@
1
1
  # applesauce-core
2
2
 
3
- AppleSauce is a collection of utilities for building reactive nostr applications. The core package provides an in-memory event database and reactive queries to help you build nostr UIs with less code.
3
+ AppleSauce is a collection of utilities for building reactive nostr applications. The core package provides an in-memory event database and reactive models to help you build nostr UIs with less code.
4
4
 
5
5
  ## Key Components
6
6
 
7
7
  - **Helpers**: Core utility methods for parsing and extracting data from nostr events
8
8
  - **EventStore**: In-memory database for storing and subscribing to nostr events
9
- - **QueryStore**: Manages queries and ensures efficient subscription handling
10
- - **Queries**: Complex subscriptions for common nostr data patterns
9
+ - **Models**: Complex subscriptions for common nostr data patterns
11
10
 
12
11
  ## Documentation
13
12
 
@@ -19,15 +18,13 @@ For detailed documentation and guides, visit:
19
18
  ## Example
20
19
 
21
20
  ```js
22
- import { EventStore, QueryStore } from "applesauce-core";
21
+ import { EventStore } from "applesauce-core";
22
+ import { ProfileModel, TimelineModel } from "applesauce-core/models";
23
23
  import { Relay } from "nostr-tools/relay";
24
24
 
25
25
  // Create a single EventStore instance for your app
26
26
  const eventStore = new EventStore();
27
27
 
28
- // Create a QueryStore to manage subscriptions efficiently
29
- const queryStore = new QueryStore(eventStore);
30
-
31
28
  // Use any nostr library for relay connections (nostr-tools, ndk, nostrify, etc...)
32
29
  const relay = await Relay.connect("wss://relay.example.com");
33
30
 
@@ -38,18 +35,15 @@ const sub = relay.subscribe([{ authors: ["3bf0c63fcb93463407af97a5e5ee64fa883d10
38
35
  },
39
36
  });
40
37
 
41
- // Subscribe to profile changes using ProfileQuery
42
- const profile = queryStore.createQuery(
43
- ProfileQuery,
44
- "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
45
- );
38
+ // Subscribe to profile changes using ProfileModel
39
+ const profile = eventStore.model(ProfileModel, "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d");
46
40
 
47
41
  profile.subscribe((parsed) => {
48
42
  if (parsed) console.log(parsed);
49
43
  });
50
44
 
51
45
  // Subscribe to a timeline of events
52
- const timeline = queryStore.createQuery(TimelineQuery, { kinds: [1] });
46
+ const timeline = eventStore.model(TimelineModel, { kinds: [1] });
53
47
 
54
48
  timeline.subscribe((events) => {
55
49
  console.log(events);
@@ -1,11 +1,12 @@
1
1
  import { Filter, NostrEvent } from "nostr-tools";
2
2
  import { Subject } from "rxjs";
3
3
  import { LRU } from "../helpers/lru.js";
4
+ import { IEventSet } from "./interface.js";
4
5
  /**
5
- * An in-memory database for nostr events
6
- * NOTE: does not handle replaceable events
6
+ * A set of nostr events that can be queried and subscribed to
7
+ * NOTE: does not handle replaceable events or any deletion logic
7
8
  */
8
- export declare class Database {
9
+ export declare class EventSet implements IEventSet {
9
10
  protected log: import("debug").Debugger;
10
11
  /** Indexes */
11
12
  protected kinds: Map<number, Set<import("nostr-tools").Event>>;
@@ -17,50 +18,64 @@ export declare class Database {
17
18
  /** A sorted array of replaceable events by uid */
18
19
  protected replaceable: Map<string, import("nostr-tools").Event[]>;
19
20
  /** A stream of events inserted into the database */
20
- inserted: Subject<import("nostr-tools").Event>;
21
+ insert$: Subject<import("nostr-tools").Event>;
21
22
  /** A stream of events that have been updated */
22
- updated: Subject<import("nostr-tools").Event>;
23
+ update$: Subject<import("nostr-tools").Event>;
23
24
  /** A stream of events removed from the database */
24
- removed: Subject<import("nostr-tools").Event>;
25
+ remove$: Subject<import("nostr-tools").Event>;
25
26
  /** A method thats called before a new event is inserted */
26
- onBeforeInsert?: (event: NostrEvent) => void;
27
+ onBeforeInsert?: (event: NostrEvent) => boolean;
28
+ /** The number of events in the event set */
27
29
  get size(): number;
28
- protected claims: WeakMap<import("nostr-tools").Event, any>;
29
- /** Index helper methods */
30
- protected getKindIndex(kind: number): Set<import("nostr-tools").Event>;
31
- protected getAuthorsIndex(author: string): Set<import("nostr-tools").Event>;
32
- protected getTagIndex(tagAndValue: string): Set<import("nostr-tools").Event>;
33
30
  /** Moves an event to the top of the LRU cache */
34
31
  touch(event: NostrEvent): void;
35
32
  /** Checks if the database contains an event without touching it */
36
33
  hasEvent(id: string): boolean;
37
34
  /** Gets a single event based on id */
38
35
  getEvent(id: string): NostrEvent | undefined;
39
- /** Checks if the database contains a replaceable event without touching it */
40
- hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
41
- /** Gets an array of replaceable events */
42
- getReplaceable(kind: number, pubkey: string, d?: string): NostrEvent[] | undefined;
36
+ /** Checks if the event set has a replaceable event */
37
+ hasReplaceable(kind: number, pubkey: string, identifier?: string): boolean;
38
+ /** Gets the latest replaceable event */
39
+ getReplaceable(kind: number, pubkey: string, identifier?: string): NostrEvent | undefined;
40
+ /** Gets the history of a replaceable event */
41
+ getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[] | undefined;
42
+ /** Gets all events that match the filters */
43
+ getByFilters(filters: Filter | Filter[]): Set<NostrEvent>;
44
+ /** Gets a timeline of events that match the filters */
45
+ getTimeline(filters: Filter | Filter[]): NostrEvent[];
43
46
  /** Inserts an event into the database and notifies all subscriptions */
44
- addEvent(event: NostrEvent): NostrEvent;
47
+ add(event: NostrEvent): NostrEvent | null;
45
48
  /** Inserts and event into the database and notifies all subscriptions that the event has updated */
46
- updateEvent(event: NostrEvent): NostrEvent;
49
+ update(event: NostrEvent): boolean;
47
50
  /** Removes an event from the database and notifies all subscriptions */
48
- removeEvent(eventOrId: string | NostrEvent): boolean;
51
+ remove(eventOrId: string | NostrEvent): boolean;
52
+ /** A weak map of events that are claimed by other things */
53
+ protected claims: WeakMap<import("nostr-tools").Event, any>;
49
54
  /** Sets the claim on the event and touches it */
50
- claimEvent(event: NostrEvent, claim: any): void;
55
+ claim(event: NostrEvent, claim: any): void;
51
56
  /** Checks if an event is claimed by anything */
52
57
  isClaimed(event: NostrEvent): boolean;
53
58
  /** Removes a claim from an event */
54
59
  removeClaim(event: NostrEvent, claim: any): void;
55
60
  /** Removes all claims on an event */
56
61
  clearClaim(event: NostrEvent): void;
62
+ /** Index helper methods */
63
+ protected getKindIndex(kind: number): Set<import("nostr-tools").Event>;
64
+ protected getAuthorsIndex(author: string): Set<import("nostr-tools").Event>;
65
+ protected getTagIndex(tagAndValue: string): Set<import("nostr-tools").Event>;
66
+ /** Iterates over all events by author */
57
67
  iterateAuthors(authors: Iterable<string>): Generator<NostrEvent>;
68
+ /** Iterates over all events by indexable tag and value */
58
69
  iterateTag(tag: string, values: Iterable<string>): Generator<NostrEvent>;
70
+ /** Iterates over all events by kind */
59
71
  iterateKinds(kinds: Iterable<number>): Generator<NostrEvent>;
72
+ /** Iterates over all events by time */
60
73
  iterateTime(since: number | undefined, until: number | undefined): Generator<NostrEvent>;
74
+ /** Iterates over all events by id */
61
75
  iterateIds(ids: Iterable<string>): Generator<NostrEvent>;
62
76
  /** Returns all events that match the filter */
63
77
  getEventsForFilter(filter: Filter): Set<NostrEvent>;
78
+ /** Returns all events that match the filters */
64
79
  getEventsForFilters(filters: Filter[]): Set<NostrEvent>;
65
80
  /** Remove the oldest events that are not claimed */
66
81
  prune(limit?: number): number;
@@ -1,15 +1,15 @@
1
1
  import { binarySearch, insertEventIntoDescendingList } from "nostr-tools/utils";
2
2
  import { Subject } from "rxjs";
3
- import { getEventUID, getIndexableTags, createReplaceableAddress, isReplaceable } from "../helpers/event.js";
4
- import { INDEXABLE_TAGS } from "./common.js";
5
- import { logger } from "../logger.js";
3
+ import { createReplaceableAddress, getEventUID, getIndexableTags, isReplaceable } from "../helpers/event.js";
6
4
  import { LRU } from "../helpers/lru.js";
5
+ import { logger } from "../logger.js";
6
+ import { INDEXABLE_TAGS } from "./common.js";
7
7
  /**
8
- * An in-memory database for nostr events
9
- * NOTE: does not handle replaceable events
8
+ * A set of nostr events that can be queried and subscribed to
9
+ * NOTE: does not handle replaceable events or any deletion logic
10
10
  */
11
- export class Database {
12
- log = logger.extend("Database");
11
+ export class EventSet {
12
+ log = logger.extend("EventSet");
13
13
  /** Indexes */
14
14
  kinds = new Map();
15
15
  authors = new Map();
@@ -20,45 +20,17 @@ export class Database {
20
20
  /** A sorted array of replaceable events by uid */
21
21
  replaceable = new Map();
22
22
  /** A stream of events inserted into the database */
23
- inserted = new Subject();
23
+ insert$ = new Subject();
24
24
  /** A stream of events that have been updated */
25
- updated = new Subject();
25
+ update$ = new Subject();
26
26
  /** A stream of events removed from the database */
27
- removed = new Subject();
27
+ remove$ = new Subject();
28
28
  /** A method thats called before a new event is inserted */
29
29
  onBeforeInsert;
30
+ /** The number of events in the event set */
30
31
  get size() {
31
32
  return this.events.size;
32
33
  }
33
- claims = new WeakMap();
34
- /** Index helper methods */
35
- getKindIndex(kind) {
36
- if (!this.kinds.has(kind))
37
- this.kinds.set(kind, new Set());
38
- return this.kinds.get(kind);
39
- }
40
- getAuthorsIndex(author) {
41
- if (!this.authors.has(author))
42
- this.authors.set(author, new Set());
43
- return this.authors.get(author);
44
- }
45
- getTagIndex(tagAndValue) {
46
- if (!this.tags.has(tagAndValue)) {
47
- // build new tag index from existing events
48
- const events = new Set();
49
- const ts = Date.now();
50
- for (const event of this.events.values()) {
51
- if (getIndexableTags(event).has(tagAndValue)) {
52
- events.add(event);
53
- }
54
- }
55
- const took = Date.now() - ts;
56
- if (took > 100)
57
- this.log(`Built index ${tagAndValue} took ${took}ms`);
58
- this.tags.set(tagAndValue, events);
59
- }
60
- return this.tags.get(tagAndValue);
61
- }
62
34
  /** Moves an event to the top of the LRU cache */
63
35
  touch(event) {
64
36
  this.events.set(event.id, event);
@@ -71,33 +43,54 @@ export class Database {
71
43
  getEvent(id) {
72
44
  return this.events.get(id);
73
45
  }
74
- /** Checks if the database contains a replaceable event without touching it */
75
- hasReplaceable(kind, pubkey, d) {
76
- const events = this.replaceable.get(createReplaceableAddress(kind, pubkey, d));
46
+ /** Checks if the event set has a replaceable event */
47
+ hasReplaceable(kind, pubkey, identifier) {
48
+ const events = this.replaceable.get(createReplaceableAddress(kind, pubkey, identifier));
77
49
  return !!events && events.length > 0;
78
50
  }
79
- /** Gets an array of replaceable events */
80
- getReplaceable(kind, pubkey, d) {
81
- return this.replaceable.get(createReplaceableAddress(kind, pubkey, d));
51
+ /** Gets the latest replaceable event */
52
+ getReplaceable(kind, pubkey, identifier) {
53
+ const address = createReplaceableAddress(kind, pubkey, identifier);
54
+ const events = this.replaceable.get(address);
55
+ return events?.[0];
56
+ }
57
+ /** Gets the history of a replaceable event */
58
+ getReplaceableHistory(kind, pubkey, identifier) {
59
+ const address = createReplaceableAddress(kind, pubkey, identifier);
60
+ return this.replaceable.get(address);
61
+ }
62
+ /** Gets all events that match the filters */
63
+ getByFilters(filters) {
64
+ return this.getEventsForFilters(Array.isArray(filters) ? filters : [filters]);
65
+ }
66
+ /** Gets a timeline of events that match the filters */
67
+ getTimeline(filters) {
68
+ const timeline = [];
69
+ const events = this.getEventsForFilters(Array.isArray(filters) ? filters : [filters]);
70
+ for (const event of events)
71
+ insertEventIntoDescendingList(timeline, event);
72
+ return timeline;
82
73
  }
83
74
  /** Inserts an event into the database and notifies all subscriptions */
84
- addEvent(event) {
75
+ add(event) {
85
76
  const id = event.id;
86
77
  const current = this.events.get(id);
87
78
  if (current)
88
79
  return current;
89
- this.onBeforeInsert?.(event);
80
+ // Ignore events if before insert returns false
81
+ if (this.onBeforeInsert?.(event) === false)
82
+ return null;
90
83
  this.events.set(id, event);
91
84
  this.getKindIndex(event.kind).add(event);
92
85
  this.getAuthorsIndex(event.pubkey).add(event);
86
+ // Add the event to the tag indexes if they exist
93
87
  for (const tag of getIndexableTags(event)) {
94
- if (this.tags.has(tag)) {
88
+ if (this.tags.has(tag))
95
89
  this.getTagIndex(tag).add(event);
96
- }
97
90
  }
98
- // insert into time index
91
+ // Insert into time index
99
92
  insertEventIntoDescendingList(this.created_at, event);
100
- // insert into replaceable index
93
+ // Insert into replaceable index
101
94
  if (isReplaceable(event.kind)) {
102
95
  const uid = getEventUID(event);
103
96
  let array = this.replaceable.get(uid);
@@ -109,17 +102,19 @@ export class Database {
109
102
  // insert the event into the sorted array
110
103
  insertEventIntoDescendingList(array, event);
111
104
  }
112
- this.inserted.next(event);
105
+ // Notify subscribers that the event was inserted
106
+ this.insert$.next(event);
113
107
  return event;
114
108
  }
115
109
  /** Inserts and event into the database and notifies all subscriptions that the event has updated */
116
- updateEvent(event) {
117
- const inserted = this.addEvent(event);
118
- this.updated.next(inserted);
119
- return inserted;
110
+ update(event) {
111
+ const inserted = this.add(event);
112
+ if (inserted)
113
+ this.update$.next(inserted);
114
+ return inserted !== null;
120
115
  }
121
116
  /** Removes an event from the database and notifies all subscriptions */
122
- removeEvent(eventOrId) {
117
+ remove(eventOrId) {
123
118
  let event = typeof eventOrId === "string" ? this.events.get(eventOrId) : eventOrId;
124
119
  if (!event)
125
120
  throw new Error("Missing event");
@@ -150,11 +145,13 @@ export class Database {
150
145
  // remove any claims this event has
151
146
  this.claims.delete(event);
152
147
  // notify subscribers this event was removed
153
- this.removed.next(event);
148
+ this.remove$.next(event);
154
149
  return true;
155
150
  }
151
+ /** A weak map of events that are claimed by other things */
152
+ claims = new WeakMap();
156
153
  /** Sets the claim on the event and touches it */
157
- claimEvent(event, claim) {
154
+ claim(event, claim) {
158
155
  if (!this.claims.has(event)) {
159
156
  this.claims.set(event, claim);
160
157
  }
@@ -175,6 +172,35 @@ export class Database {
175
172
  clearClaim(event) {
176
173
  this.claims.delete(event);
177
174
  }
175
+ /** Index helper methods */
176
+ getKindIndex(kind) {
177
+ if (!this.kinds.has(kind))
178
+ this.kinds.set(kind, new Set());
179
+ return this.kinds.get(kind);
180
+ }
181
+ getAuthorsIndex(author) {
182
+ if (!this.authors.has(author))
183
+ this.authors.set(author, new Set());
184
+ return this.authors.get(author);
185
+ }
186
+ getTagIndex(tagAndValue) {
187
+ if (!this.tags.has(tagAndValue)) {
188
+ // build new tag index from existing events
189
+ const events = new Set();
190
+ const ts = Date.now();
191
+ for (const event of this.events.values()) {
192
+ if (getIndexableTags(event).has(tagAndValue)) {
193
+ events.add(event);
194
+ }
195
+ }
196
+ const took = Date.now() - ts;
197
+ if (took > 100)
198
+ this.log(`Built index ${tagAndValue} took ${took}ms`);
199
+ this.tags.set(tagAndValue, events);
200
+ }
201
+ return this.tags.get(tagAndValue);
202
+ }
203
+ /** Iterates over all events by author */
178
204
  *iterateAuthors(authors) {
179
205
  for (const author of authors) {
180
206
  const events = this.authors.get(author);
@@ -184,6 +210,7 @@ export class Database {
184
210
  }
185
211
  }
186
212
  }
213
+ /** Iterates over all events by indexable tag and value */
187
214
  *iterateTag(tag, values) {
188
215
  for (const value of values) {
189
216
  const events = this.getTagIndex(tag + ":" + value);
@@ -193,6 +220,7 @@ export class Database {
193
220
  }
194
221
  }
195
222
  }
223
+ /** Iterates over all events by kind */
196
224
  *iterateKinds(kinds) {
197
225
  for (const kind of kinds) {
198
226
  const events = this.kinds.get(kind);
@@ -202,6 +230,7 @@ export class Database {
202
230
  }
203
231
  }
204
232
  }
233
+ /** Iterates over all events by time */
205
234
  *iterateTime(since, until) {
206
235
  let untilIndex = 0;
207
236
  let sinceIndex = this.created_at.length - 1;
@@ -223,6 +252,7 @@ export class Database {
223
252
  yield this.created_at[i];
224
253
  }
225
254
  }
255
+ /** Iterates over all events by id */
226
256
  *iterateIds(ids) {
227
257
  for (const id of ids) {
228
258
  if (this.events.has(id))
@@ -286,6 +316,7 @@ export class Database {
286
316
  }
287
317
  return events;
288
318
  }
319
+ /** Returns all events that match the filters */
289
320
  getEventsForFilters(filters) {
290
321
  if (filters.length === 0)
291
322
  throw new Error("No Filters");
@@ -304,7 +335,7 @@ export class Database {
304
335
  while (cursor) {
305
336
  const event = cursor.value;
306
337
  if (!this.isClaimed(event)) {
307
- this.removeEvent(event);
338
+ this.remove(event);
308
339
  removed++;
309
340
  if (removed >= limit)
310
341
  break;
@@ -1,20 +1,25 @@
1
1
  import { Filter, NostrEvent } from "nostr-tools";
2
2
  import { Observable } from "rxjs";
3
- import { Database } from "./database.js";
4
- import { IEventStore } from "./interface.js";
3
+ import { EventSet } from "./event-set.js";
4
+ import { IEventStore, ModelConstructor } from "./interface.js";
5
+ import { AddressPointer, EventPointer } from "nostr-tools/nip19";
6
+ /** A symbol on an event that marks which event store its part of */
5
7
  export declare const EventStoreSymbol: unique symbol;
6
8
  export declare class EventStore implements IEventStore {
7
- database: Database;
9
+ database: EventSet;
8
10
  /** Enable this to keep old versions of replaceable events */
9
11
  keepOldVersions: boolean;
10
- /** A method used to verify new events before added them */
12
+ /**
13
+ * A method used to verify new events before added them
14
+ * @returns true if the event is valid, false if it should be ignored
15
+ */
11
16
  verifyEvent?: (event: NostrEvent) => boolean;
12
17
  /** A stream of new events added to the store */
13
- inserts: Observable<NostrEvent>;
18
+ insert$: Observable<NostrEvent>;
14
19
  /** A stream of events that have been updated */
15
- updates: Observable<NostrEvent>;
20
+ update$: Observable<NostrEvent>;
16
21
  /** A stream of events that have been removed */
17
- removes: Observable<NostrEvent>;
22
+ remove$: Observable<NostrEvent>;
18
23
  constructor();
19
24
  protected deletedIds: Set<string>;
20
25
  protected deletedCoords: Map<string, number>;
@@ -23,31 +28,46 @@ export declare class EventStore implements IEventStore {
23
28
  /** Copies important metadata from and identical event to another */
24
29
  static mergeDuplicateEvent(source: NostrEvent, dest: NostrEvent): void;
25
30
  /**
26
- * Adds an event to the database and update subscriptions
27
- * @throws
31
+ * Adds an event to the store and update subscriptions
32
+ * @returns The existing event or the event that was added, if it was ignored returns null
28
33
  */
29
- add(event: NostrEvent, fromRelay?: string): NostrEvent;
34
+ add(event: NostrEvent, fromRelay?: string): NostrEvent | null;
30
35
  /** Removes an event from the database and updates subscriptions */
31
36
  remove(event: string | NostrEvent): boolean;
37
+ /** Add an event to the store and notifies all subscribes it has updated */
38
+ update(event: NostrEvent): boolean;
32
39
  /** Removes any event that is not being used by a subscription */
33
40
  prune(max?: number): number;
34
- /** Add an event to the store and notifies all subscribes it has updated */
35
- update(event: NostrEvent): NostrEvent;
36
- /** Get all events matching a filter */
37
- getAll(filters: Filter | Filter[]): Set<NostrEvent>;
38
- /** Check if the store has an event */
41
+ /** Check if the store has an event by id */
39
42
  hasEvent(id: string): boolean;
43
+ /** Get an event by id from the store */
40
44
  getEvent(id: string): NostrEvent | undefined;
41
45
  /** Check if the store has a replaceable event */
42
46
  hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
43
47
  /** Gets the latest version of a replaceable event */
44
- getReplaceable(kind: number, pubkey: string, d?: string): NostrEvent | undefined;
48
+ getReplaceable(kind: number, pubkey: string, identifier?: string): NostrEvent | undefined;
45
49
  /** Returns all versions of a replaceable event */
46
- getReplaceableHistory(kind: number, pubkey: string, d?: string): NostrEvent[] | undefined;
50
+ getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[] | undefined;
51
+ /** Get all events matching a filter */
52
+ getByFilters(filters: Filter | Filter[]): Set<NostrEvent>;
47
53
  /** Returns a timeline of events that match filters */
48
54
  getTimeline(filters: Filter | Filter[]): NostrEvent[];
55
+ /** Sets the claim on the event and touches it */
56
+ claim(event: NostrEvent, claim: any): void;
57
+ /** Checks if an event is claimed by anything */
58
+ isClaimed(event: NostrEvent): boolean;
59
+ /** Removes a claim from an event */
60
+ removeClaim(event: NostrEvent, claim: any): void;
61
+ /** Removes all claims on an event */
62
+ clearClaim(event: NostrEvent): void;
63
+ /** A directory of all active models */
64
+ protected models: Map<ModelConstructor<any, any[]>, Map<string, Observable<any>>>;
65
+ /** How long a model should be kept "warm" while nothing is subscribed to it */
66
+ modelKeepWarm: number;
67
+ /** Get or create a model on the event store */
68
+ model<T extends unknown, Args extends Array<any>>(constructor: ModelConstructor<T, Args>, ...args: Args): Observable<T>;
49
69
  /**
50
- * Creates an observable that streams all events that match the filter and remains open
70
+ * Creates an observable that streams all events that match the filter
51
71
  * @param filters
52
72
  * @param [onlyNew=false] Only subscribe to new events
53
73
  */
@@ -56,18 +76,37 @@ export declare class EventStore implements IEventStore {
56
76
  removed(id: string): Observable<never>;
57
77
  /** Creates an observable that emits when event is updated */
58
78
  updated(event: string | NostrEvent): Observable<NostrEvent>;
59
- /** Creates an observable that subscribes to a single event */
79
+ /** Creates a {@link EventModel} */
60
80
  event(id: string): Observable<NostrEvent | undefined>;
61
- /** Creates an observable that subscribes to multiple events */
81
+ /** Creates a {@link ReplaceableModel} */
82
+ replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
83
+ /** Creates a {@link TimelineModel} */
84
+ timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
85
+ /** Creates a {@link EventsModel} */
62
86
  events(ids: string[]): Observable<Record<string, NostrEvent>>;
63
- /** Creates an observable that subscribes to the latest version of a replaceable event */
64
- replaceable(kind: number, pubkey: string, d?: string): Observable<NostrEvent | undefined>;
65
- /** Creates an observable that subscribes to the latest version of an array of replaceable events*/
87
+ /** Creates a {@link ReplaceableSetModel} */
66
88
  replaceableSet(pointers: {
67
89
  kind: number;
68
90
  pubkey: string;
69
91
  identifier?: string;
70
92
  }[]): Observable<Record<string, NostrEvent>>;
71
- /** Creates an observable that updates with an array of sorted events */
72
- timeline(filters: Filter | Filter[], keepOldVersions?: boolean): Observable<NostrEvent[]>;
93
+ /** Creates a {@link ProfileModel} */
94
+ profile(pubkey: string): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
95
+ /** Creates a {@link ContactsModel} */
96
+ contacts(pubkey: string): Observable<import("nostr-tools/nip19").ProfilePointer[]>;
97
+ /** Creates a {@link MuteModel} */
98
+ mutes(pubkey: string): Observable<import("../helpers/mutes.js").Mutes | undefined>;
99
+ /** Creates a {@link ReactionsModel} */
100
+ reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
101
+ /** Creates a {@link MailboxesModel} */
102
+ mailboxes(pubkey: string): Observable<{
103
+ inboxes: string[];
104
+ outboxes: string[];
105
+ } | undefined>;
106
+ /** Creates a {@link UserBlossomServersModel} */
107
+ blossomServers(pubkey: string): Observable<URL[]>;
108
+ /** Creates a {@link ThreadModel} */
109
+ thread(root: string | EventPointer | AddressPointer): Observable<import("../models/thread.js").Thread>;
110
+ /** Creates a {@link CommentsModel} */
111
+ comments(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
73
112
  }