applesauce-core 0.11.0 → 0.12.1

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 (41) hide show
  1. package/dist/event-store/__tests__/event-store.test.js +45 -1
  2. package/dist/event-store/database.d.ts +2 -1
  3. package/dist/event-store/database.js +6 -7
  4. package/dist/event-store/event-store.d.ts +15 -5
  5. package/dist/event-store/event-store.js +72 -24
  6. package/dist/event-store/index.d.ts +1 -0
  7. package/dist/event-store/index.js +1 -0
  8. package/dist/event-store/interface.d.ts +29 -0
  9. package/dist/helpers/__tests__/nip-19.test.d.ts +1 -0
  10. package/dist/helpers/__tests__/nip-19.test.js +42 -0
  11. package/dist/helpers/cache.d.ts +3 -4
  12. package/dist/helpers/cache.js +1 -1
  13. package/dist/helpers/direct-messages.d.ts +4 -0
  14. package/dist/helpers/direct-messages.js +5 -0
  15. package/dist/helpers/event.d.ts +10 -2
  16. package/dist/helpers/event.js +5 -0
  17. package/dist/helpers/gift-wraps.d.ts +12 -0
  18. package/dist/helpers/gift-wraps.js +49 -0
  19. package/dist/helpers/hidden-content.d.ts +48 -0
  20. package/dist/helpers/hidden-content.js +88 -0
  21. package/dist/helpers/hidden-tags.d.ts +10 -28
  22. package/dist/helpers/hidden-tags.js +24 -59
  23. package/dist/helpers/index.d.ts +3 -0
  24. package/dist/helpers/index.js +3 -0
  25. package/dist/helpers/mutes.d.ts +1 -0
  26. package/dist/helpers/mutes.js +2 -1
  27. package/dist/helpers/nip-19.d.ts +4 -0
  28. package/dist/helpers/nip-19.js +27 -0
  29. package/dist/observable/claim-latest.d.ts +3 -2
  30. package/dist/observable/claim-latest.js +2 -1
  31. package/dist/observable/with-immediate-value.d.ts +3 -0
  32. package/dist/observable/with-immediate-value.js +19 -0
  33. package/dist/queries/mutes.js +1 -1
  34. package/dist/queries/simple.d.ts +1 -1
  35. package/dist/queries/simple.js +3 -3
  36. package/dist/query-store/__tests__/query-store.test.d.ts +1 -0
  37. package/dist/query-store/{query-store.test.js → __tests__/query-store.test.js} +33 -3
  38. package/dist/query-store/query-store.d.ts +6 -4
  39. package/dist/query-store/query-store.js +12 -3
  40. package/package.json +5 -4
  41. /package/dist/{query-store/query-store.test.d.ts → event-store/interface.js} +0 -0
@@ -48,6 +48,30 @@ describe("add", () => {
48
48
  expect(eventStore.getEvent(profile.id)).toBeUndefined();
49
49
  });
50
50
  });
51
+ describe("inserts", () => {
52
+ it("should emit newer replaceable events", () => {
53
+ const spy = subscribeSpyTo(eventStore.inserts);
54
+ eventStore.add(profile);
55
+ const newer = user.profile({ name: "new name" }, { created_at: profile.created_at + 100 });
56
+ eventStore.add(newer);
57
+ expect(spy.getValues()).toEqual([profile, newer]);
58
+ });
59
+ it("should not emit when older replaceable event is added", () => {
60
+ const spy = subscribeSpyTo(eventStore.inserts);
61
+ eventStore.add(profile);
62
+ eventStore.add(user.profile({ name: "new name" }, { created_at: profile.created_at - 1000 }));
63
+ expect(spy.getValues()).toEqual([profile]);
64
+ });
65
+ });
66
+ describe("removes", () => {
67
+ it("should emit older replaceable events when the newest replaceable event is added", () => {
68
+ const spy = subscribeSpyTo(eventStore.removes);
69
+ eventStore.add(profile);
70
+ const newer = user.profile({ name: "new name" }, { created_at: profile.created_at + 1000 });
71
+ eventStore.add(newer);
72
+ expect(spy.getValues()).toEqual([profile]);
73
+ });
74
+ });
51
75
  describe("verifyEvent", () => {
52
76
  it("should be called for all events added", () => {
53
77
  const verifyEvent = vi.fn().mockReturnValue(true);
@@ -69,7 +93,7 @@ describe("verifyEvent", () => {
69
93
  expect(verifyEvent).toHaveBeenCalledTimes(1);
70
94
  });
71
95
  });
72
- describe("deleted", () => {
96
+ describe("removed", () => {
73
97
  it("should complete when event is removed", () => {
74
98
  eventStore.add(profile);
75
99
  const spy = subscribeSpyTo(eventStore.removed(profile.id));
@@ -187,6 +211,13 @@ describe("replaceable", () => {
187
211
  eventStore.add(user.profile({ name: "really old name" }, { created_at: profile.created_at - 1000 }));
188
212
  expect(spy.getValues()).toEqual([profile]);
189
213
  });
214
+ it("should emit newer events", () => {
215
+ const spy = subscribeSpyTo(eventStore.replaceable(0, user.pubkey));
216
+ eventStore.add(profile);
217
+ const newProfile = user.profile({ name: "new name" }, { created_at: profile.created_at + 500 });
218
+ eventStore.add(newProfile);
219
+ expect(spy.getValues()).toEqual([profile, newProfile]);
220
+ });
190
221
  });
191
222
  describe("timeline", () => {
192
223
  it("should emit an empty array if there are not events", () => {
@@ -223,6 +254,19 @@ describe("timeline", () => {
223
254
  eventStore.add(user.profile({ name: "old-name" }, { created_at: profile.created_at - 1000 }));
224
255
  expect(spy.getValues()).toEqual([[profile]]);
225
256
  });
257
+ it("should return new array for every value", () => {
258
+ const first = user.note("first note");
259
+ const second = user.note("second note");
260
+ const third = user.note("third note");
261
+ eventStore.add(first);
262
+ const spy = subscribeSpyTo(eventStore.timeline({ kinds: [0] }));
263
+ eventStore.add(second);
264
+ eventStore.add(third);
265
+ const hasDuplicates = (arr) => {
266
+ return new Set(arr).size !== arr.length;
267
+ };
268
+ expect(hasDuplicates(spy.getValues())).toBe(false);
269
+ });
226
270
  });
227
271
  describe("replaceableSet", () => {
228
272
  it("should not emit if there are not events", () => {
@@ -14,6 +14,7 @@ export declare class Database {
14
14
  protected created_at: NostrEvent[];
15
15
  /** LRU cache of last events touched */
16
16
  events: LRU<import("nostr-tools").Event>;
17
+ /** A sorted array of replaceable events by uid */
17
18
  protected replaceable: Map<string, import("nostr-tools").Event[]>;
18
19
  /** A stream of events inserted into the database */
19
20
  inserted: Subject<import("nostr-tools").Event>;
@@ -60,7 +61,7 @@ export declare class Database {
60
61
  iterateIds(ids: Iterable<string>): Generator<NostrEvent>;
61
62
  /** Returns all events that match the filter */
62
63
  getEventsForFilter(filter: Filter): Set<NostrEvent>;
63
- getForFilters(filters: Filter[]): Set<NostrEvent>;
64
+ getEventsForFilters(filters: Filter[]): Set<NostrEvent>;
64
65
  /** Remove the oldest events that are not claimed */
65
66
  prune(limit?: number): number;
66
67
  }
@@ -1,6 +1,6 @@
1
1
  import { binarySearch, insertEventIntoDescendingList } from "nostr-tools/utils";
2
2
  import { Subject } from "rxjs";
3
- import { FromCacheSymbol, getEventUID, getIndexableTags, getReplaceableUID, isReplaceable } from "../helpers/event.js";
3
+ import { getEventUID, getIndexableTags, getReplaceableUID, isReplaceable } 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";
@@ -17,6 +17,7 @@ export class Database {
17
17
  created_at = [];
18
18
  /** LRU cache of last events touched */
19
19
  events = new LRU();
20
+ /** A sorted array of replaceable events by uid */
20
21
  replaceable = new Map();
21
22
  /** A stream of events inserted into the database */
22
23
  inserted = new Subject();
@@ -83,12 +84,8 @@ export class Database {
83
84
  addEvent(event) {
84
85
  const id = event.id;
85
86
  const current = this.events.get(id);
86
- if (current) {
87
- // if this is a duplicate event, transfer some important symbols
88
- if (event[FromCacheSymbol])
89
- current[FromCacheSymbol] = event[FromCacheSymbol];
87
+ if (current)
90
88
  return current;
91
- }
92
89
  this.onBeforeInsert?.(event);
93
90
  this.events.set(id, event);
94
91
  this.getKindIndex(event.kind).add(event);
@@ -105,9 +102,11 @@ export class Database {
105
102
  const uid = getEventUID(event);
106
103
  let array = this.replaceable.get(uid);
107
104
  if (!this.replaceable.has(uid)) {
105
+ // add an empty array if there is no array
108
106
  array = [];
109
107
  this.replaceable.set(uid, array);
110
108
  }
109
+ // insert the event into the sorted array
111
110
  insertEventIntoDescendingList(array, event);
112
111
  }
113
112
  this.inserted.next(event);
@@ -287,7 +286,7 @@ export class Database {
287
286
  }
288
287
  return events;
289
288
  }
290
- getForFilters(filters) {
289
+ getEventsForFilters(filters) {
291
290
  if (filters.length === 0)
292
291
  throw new Error("No Filters");
293
292
  let events = new Set();
@@ -1,12 +1,20 @@
1
1
  import { Filter, NostrEvent } from "nostr-tools";
2
2
  import { Observable } from "rxjs";
3
3
  import { Database } from "./database.js";
4
- export declare class EventStore {
4
+ import { IEventStore } from "./interface.js";
5
+ export declare const EventStoreSymbol: unique symbol;
6
+ export declare class EventStore implements IEventStore {
5
7
  database: Database;
6
8
  /** Enable this to keep old versions of replaceable events */
7
9
  keepOldVersions: boolean;
8
10
  /** A method used to verify new events before added them */
9
11
  verifyEvent?: (event: NostrEvent) => boolean;
12
+ /** A stream of new events added to the store */
13
+ inserts: Observable<NostrEvent>;
14
+ /** A stream of events that have been updated */
15
+ updates: Observable<NostrEvent>;
16
+ /** A stream of events that have been removed */
17
+ removes: Observable<NostrEvent>;
10
18
  constructor();
11
19
  protected deletedIds: Set<string>;
12
20
  protected deletedCoords: Map<string, number>;
@@ -26,16 +34,18 @@ export declare class EventStore {
26
34
  /** Add an event to the store and notifies all subscribes it has updated */
27
35
  update(event: NostrEvent): NostrEvent;
28
36
  /** Get all events matching a filter */
29
- getAll(filters: Filter[]): Set<NostrEvent>;
37
+ getAll(filters: Filter | Filter[]): Set<NostrEvent>;
30
38
  /** Check if the store has an event */
31
- hasEvent(uid: string): boolean;
32
- getEvent(uid: string): NostrEvent | undefined;
39
+ hasEvent(id: string): boolean;
40
+ getEvent(id: string): NostrEvent | undefined;
33
41
  /** Check if the store has a replaceable event */
34
42
  hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
35
43
  /** Gets the latest version of a replaceable event */
36
44
  getReplaceable(kind: number, pubkey: string, d?: string): NostrEvent | undefined;
37
45
  /** Returns all versions of a replaceable event */
38
46
  getReplaceableHistory(kind: number, pubkey: string, d?: string): NostrEvent[] | undefined;
47
+ /** Returns a timeline of events that match filters */
48
+ getTimeline(filters: Filter | Filter[]): NostrEvent[];
39
49
  /**
40
50
  * Creates an observable that streams all events that match the filter and remains open
41
51
  * @param filters
@@ -45,7 +55,7 @@ export declare class EventStore {
45
55
  /** Returns an observable that completes when an event is removed */
46
56
  removed(id: string): Observable<never>;
47
57
  /** Creates an observable that emits when event is updated */
48
- updated(id: string): Observable<NostrEvent>;
58
+ updated(event: string | NostrEvent): Observable<NostrEvent>;
49
59
  /** Creates an observable that subscribes to a single event */
50
60
  event(id: string): Observable<NostrEvent | undefined>;
51
61
  /** Creates an observable that subscribes to multiple events */
@@ -3,12 +3,13 @@ import { insertEventIntoDescendingList } from "nostr-tools/utils";
3
3
  import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
4
4
  import { defer, distinctUntilChanged, EMPTY, endWith, filter, finalize, from, map, merge, mergeMap, mergeWith, of, repeat, scan, take, takeUntil, tap, } from "rxjs";
5
5
  import { Database } from "./database.js";
6
- import { getEventUID, getReplaceableIdentifier, getReplaceableUID, getTagValue, isReplaceable, } from "../helpers/event.js";
6
+ import { FromCacheSymbol, getEventUID, getReplaceableIdentifier, getReplaceableUID, getTagValue, isReplaceable, } from "../helpers/event.js";
7
7
  import { matchFilters } from "../helpers/filter.js";
8
8
  import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
9
9
  import { getDeleteCoordinates, getDeleteIds } from "../helpers/delete.js";
10
10
  import { claimEvents } from "../observable/claim-events.js";
11
11
  import { claimLatest } from "../observable/claim-latest.js";
12
+ export const EventStoreSymbol = Symbol.for("event-store");
12
13
  function sortDesc(a, b) {
13
14
  return b.created_at - a.created_at;
14
15
  }
@@ -18,6 +19,12 @@ export class EventStore {
18
19
  keepOldVersions = false;
19
20
  /** A method used to verify new events before added them */
20
21
  verifyEvent;
22
+ /** A stream of new events added to the store */
23
+ inserts;
24
+ /** A stream of events that have been updated */
25
+ updates;
26
+ /** A stream of events that have been removed */
27
+ removes;
21
28
  constructor() {
22
29
  this.database = new Database();
23
30
  this.database.onBeforeInsert = (event) => {
@@ -25,6 +32,17 @@ export class EventStore {
25
32
  if (this.verifyEvent && this.verifyEvent(event) === false)
26
33
  throw new Error("Invalid event");
27
34
  };
35
+ // when events are added to the database, add the symbol
36
+ this.database.inserted.subscribe((event) => {
37
+ Reflect.set(event, EventStoreSymbol, this);
38
+ });
39
+ // when events are removed from the database, remove the symbol
40
+ this.database.removed.subscribe((event) => {
41
+ Reflect.deleteProperty(event, EventStoreSymbol);
42
+ });
43
+ this.inserts = this.database.inserted;
44
+ this.updates = this.database.updated;
45
+ this.removes = this.database.removed;
28
46
  }
29
47
  // delete state
30
48
  deletedIds = new Set();
@@ -69,6 +87,10 @@ export class EventStore {
69
87
  for (const relay of relays)
70
88
  addSeenRelay(dest, relay);
71
89
  }
90
+ // copy the from cache symbol only if its true
91
+ const fromCache = Reflect.get(source, FromCacheSymbol);
92
+ if (fromCache && !Reflect.get(dest, FromCacheSymbol))
93
+ Reflect.set(dest, FromCacheSymbol, fromCache);
72
94
  }
73
95
  /**
74
96
  * Adds an event to the database and update subscriptions
@@ -80,6 +102,25 @@ export class EventStore {
80
102
  // Ignore if the event was deleted
81
103
  if (this.checkDeleted(event))
82
104
  return event;
105
+ // Get the replaceable identifier
106
+ const d = isReplaceable(event.kind) ? getTagValue(event, "d") : undefined;
107
+ // Don't insert the event if there is already a newer version
108
+ if (!this.keepOldVersions && isReplaceable(event.kind)) {
109
+ const existing = this.database.getReplaceable(event.kind, event.pubkey, d);
110
+ // If there is already a newer version, copy cached symbols and return existing event
111
+ if (existing && existing.length > 0 && existing[0].created_at >= event.created_at) {
112
+ EventStore.mergeDuplicateEvent(event, existing[0]);
113
+ return existing[0];
114
+ }
115
+ }
116
+ else if (this.database.hasEvent(event.id)) {
117
+ // Duplicate event, copy symbols and return existing event
118
+ const existing = this.database.getEvent(event.id);
119
+ if (existing) {
120
+ EventStore.mergeDuplicateEvent(event, existing);
121
+ return existing;
122
+ }
123
+ }
83
124
  // Insert event into database
84
125
  const inserted = this.database.addEvent(event);
85
126
  // Copy cached data if its a duplicate event
@@ -90,7 +131,7 @@ export class EventStore {
90
131
  addSeenRelay(inserted, fromRelay);
91
132
  // remove all old version of the replaceable event
92
133
  if (!this.keepOldVersions && isReplaceable(event.kind)) {
93
- const existing = this.database.getReplaceable(event.kind, event.pubkey, getTagValue(event, "d"));
134
+ const existing = this.database.getReplaceable(event.kind, event.pubkey, d);
94
135
  if (existing) {
95
136
  const older = Array.from(existing).filter((e) => e.created_at < event.created_at);
96
137
  for (const old of older)
@@ -117,14 +158,14 @@ export class EventStore {
117
158
  }
118
159
  /** Get all events matching a filter */
119
160
  getAll(filters) {
120
- return this.database.getForFilters(filters);
161
+ return this.database.getEventsForFilters(Array.isArray(filters) ? filters : [filters]);
121
162
  }
122
163
  /** Check if the store has an event */
123
- hasEvent(uid) {
124
- return this.database.hasEvent(uid);
164
+ hasEvent(id) {
165
+ return this.database.hasEvent(id);
125
166
  }
126
- getEvent(uid) {
127
- return this.database.getEvent(uid);
167
+ getEvent(id) {
168
+ return this.database.getEvent(id);
128
169
  }
129
170
  /** Check if the store has a replaceable event */
130
171
  hasReplaceable(kind, pubkey, d) {
@@ -138,6 +179,10 @@ export class EventStore {
138
179
  getReplaceableHistory(kind, pubkey, d) {
139
180
  return this.database.getReplaceable(kind, pubkey, d);
140
181
  }
182
+ /** Returns a timeline of events that match filters */
183
+ getTimeline(filters) {
184
+ return Array.from(this.database.getEventsForFilters(Array.isArray(filters) ? filters : [filters])).sort(sortDesc);
185
+ }
141
186
  /**
142
187
  * Creates an observable that streams all events that match the filter and remains open
143
188
  * @param filters
@@ -149,14 +194,14 @@ export class EventStore {
149
194
  // merge existing events
150
195
  onlyNew ? EMPTY : from(this.getAll(filters)),
151
196
  // subscribe to future events
152
- this.database.inserted.pipe(filter((e) => matchFilters(filters, e))));
197
+ this.inserts.pipe(filter((e) => matchFilters(filters, e))));
153
198
  }
154
199
  /** Returns an observable that completes when an event is removed */
155
200
  removed(id) {
156
201
  const deleted = this.checkDeleted(id);
157
202
  if (deleted)
158
203
  return EMPTY;
159
- return this.database.removed.pipe(
204
+ return this.removes.pipe(
160
205
  // listen for removed events
161
206
  filter((e) => e.id === id),
162
207
  // complete as soon as we find a matching removed event
@@ -165,8 +210,8 @@ export class EventStore {
165
210
  mergeMap(() => EMPTY));
166
211
  }
167
212
  /** Creates an observable that emits when event is updated */
168
- updated(id) {
169
- return this.database.updated.pipe(filter((e) => e.id === id));
213
+ updated(event) {
214
+ return this.database.updated.pipe(filter((e) => e.id === event || e === event));
170
215
  }
171
216
  /** Creates an observable that subscribes to a single event */
172
217
  event(id) {
@@ -177,13 +222,13 @@ export class EventStore {
177
222
  return event ? of(event) : EMPTY;
178
223
  }),
179
224
  // subscribe to updates
180
- this.database.inserted.pipe(filter((e) => e.id === id)),
225
+ this.inserts.pipe(filter((e) => e.id === id)),
181
226
  // subscribe to updates
182
227
  this.updated(id),
183
228
  // emit undefined when deleted
184
229
  this.removed(id).pipe(endWith(undefined))).pipe(
185
230
  // claim all events
186
- claimEvents(this.database));
231
+ claimLatest(this.database));
187
232
  }
188
233
  /** Creates an observable that subscribes to multiple events */
189
234
  events(ids) {
@@ -191,15 +236,15 @@ export class EventStore {
191
236
  // lazily get existing events
192
237
  defer(() => from(ids.map((id) => this.getEvent(id)))),
193
238
  // subscribe to new events
194
- this.database.inserted.pipe(filter((e) => ids.includes(e.id))),
239
+ this.inserts.pipe(filter((e) => ids.includes(e.id))),
195
240
  // subscribe to updates
196
- this.database.updated.pipe(filter((e) => ids.includes(e.id)))).pipe(
241
+ this.updates.pipe(filter((e) => ids.includes(e.id)))).pipe(
197
242
  // ignore empty messages
198
243
  filter((e) => !!e),
199
244
  // claim all events until cleanup
200
245
  claimEvents(this.database),
201
246
  // watch for removed events
202
- mergeWith(this.database.removed.pipe(filter((e) => ids.includes(e.id)), map((e) => e.id))),
247
+ mergeWith(this.removes.pipe(filter((e) => ids.includes(e.id)), map((e) => e.id))),
203
248
  // merge all events into a directory
204
249
  scan((dir, event) => {
205
250
  if (typeof event === "string") {
@@ -224,13 +269,16 @@ export class EventStore {
224
269
  return event ? of(event) : EMPTY;
225
270
  }),
226
271
  // subscribe to new events
227
- this.database.inserted.pipe(filter((e) => e.pubkey == pubkey && e.kind === kind && (d !== undefined ? getReplaceableIdentifier(e) === d : true)))).pipe(
272
+ this.inserts.pipe(filter((e) => e.pubkey == pubkey && e.kind === kind && (d !== undefined ? getReplaceableIdentifier(e) === d : true)))).pipe(
228
273
  // only update if event is newer
229
- distinctUntilChanged((prev, event) => prev.created_at >= event.created_at),
274
+ distinctUntilChanged((prev, event) => {
275
+ // are the events the same? i.e. is the prev event older
276
+ return prev.created_at >= event.created_at;
277
+ }),
230
278
  // Hacky way to extract the current event so takeUntil can access it
231
279
  tap((event) => (current = event)),
232
280
  // complete when event is removed
233
- takeUntil(this.database.removed.pipe(filter((e) => e.id === current?.id))),
281
+ takeUntil(this.removes.pipe(filter((e) => e.id === current?.id))),
234
282
  // emit undefined when removed
235
283
  endWith(undefined),
236
284
  // keep the observable hot
@@ -245,7 +293,7 @@ export class EventStore {
245
293
  // start with existing events
246
294
  defer(() => from(pointers.map((p) => this.getReplaceable(p.kind, p.pubkey, p.identifier)))),
247
295
  // subscribe to new events
248
- this.database.inserted.pipe(filter((e) => isReplaceable(e.kind) && uids.has(getEventUID(e))))).pipe(
296
+ this.inserts.pipe(filter((e) => isReplaceable(e.kind) && uids.has(getEventUID(e))))).pipe(
249
297
  // filter out undefined
250
298
  filter((e) => !!e),
251
299
  // claim all events
@@ -253,7 +301,7 @@ export class EventStore {
253
301
  // convert events to add commands
254
302
  map((e) => ["add", e]),
255
303
  // watch for removed events
256
- mergeWith(this.database.removed.pipe(filter((e) => isReplaceable(e.kind) && uids.has(getEventUID(e))), map((e) => ["remove", e]))),
304
+ mergeWith(this.removes.pipe(filter((e) => isReplaceable(e.kind) && uids.has(getEventUID(e))), map((e) => ["remove", e]))),
257
305
  // reduce events into directory
258
306
  scan((dir, [action, event]) => {
259
307
  const uid = getEventUID(event);
@@ -278,15 +326,15 @@ export class EventStore {
278
326
  filters = Array.isArray(filters) ? filters : [filters];
279
327
  const seen = new Map();
280
328
  // get current events
281
- return defer(() => of(Array.from(this.database.getForFilters(filters)).sort(sortDesc))).pipe(
329
+ return defer(() => of(Array.from(this.database.getEventsForFilters(filters)).sort(sortDesc))).pipe(
282
330
  // claim existing events
283
331
  claimEvents(this.database),
284
332
  // subscribe to newer events
285
- mergeWith(this.database.inserted.pipe(filter((e) => matchFilters(filters, e)),
333
+ mergeWith(this.inserts.pipe(filter((e) => matchFilters(filters, e)),
286
334
  // claim all new events
287
335
  claimEvents(this.database))),
288
336
  // subscribe to delete events
289
- mergeWith(this.database.removed.pipe(filter((e) => matchFilters(filters, e)), map((e) => e.id))),
337
+ mergeWith(this.removes.pipe(filter((e) => matchFilters(filters, e)), map((e) => e.id))),
290
338
  // build a timeline
291
339
  scan((timeline, event) => {
292
340
  // filter out removed events from timeline
@@ -1,2 +1,3 @@
1
1
  export * from "./event-store.js";
2
2
  export * from "./database.js";
3
+ export * from "./interface.js";
@@ -1,2 +1,3 @@
1
1
  export * from "./event-store.js";
2
2
  export * from "./database.js";
3
+ export * from "./interface.js";
@@ -0,0 +1,29 @@
1
+ import { Filter, NostrEvent } from "nostr-tools";
2
+ import { Observable } from "rxjs";
3
+ export interface IEventStore {
4
+ inserts: Observable<NostrEvent>;
5
+ updates: Observable<NostrEvent>;
6
+ removes: Observable<NostrEvent>;
7
+ add(event: NostrEvent, fromRelay?: string): NostrEvent;
8
+ remove(event: string | NostrEvent): boolean;
9
+ update(event: NostrEvent): NostrEvent;
10
+ hasEvent(id: string): boolean;
11
+ hasReplaceable(kind: number, pubkey: string, identifier?: string): boolean;
12
+ getEvent(id: string): NostrEvent | undefined;
13
+ getReplaceable(kind: number, pubkey: string, identifier?: string): NostrEvent | undefined;
14
+ getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[] | undefined;
15
+ getAll(filters: Filter | Filter[]): Set<NostrEvent>;
16
+ getTimeline(filters: Filter | Filter[]): NostrEvent[];
17
+ filters(filters: Filter | Filter[]): Observable<NostrEvent>;
18
+ updated(id: string | NostrEvent): Observable<NostrEvent>;
19
+ removed(id: string): Observable<never>;
20
+ event(id: string): Observable<NostrEvent | undefined>;
21
+ events(ids: string[]): Observable<Record<string, NostrEvent>>;
22
+ replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
23
+ replaceableSet(pointers: {
24
+ kind: number;
25
+ pubkey: string;
26
+ identifier?: string;
27
+ }[]): Observable<Record<string, NostrEvent>>;
28
+ timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { bytesToHex } from "@noble/hashes/utils";
3
+ import { normalizeToPubkey, normalizeToSecretKey } from "../nip-19.js";
4
+ describe("normalizeToPubkey", () => {
5
+ it("should get pubkey from npub", () => {
6
+ expect(normalizeToPubkey("npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr")).toEqual("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5");
7
+ });
8
+ it("should get pubkey from nprofile", () => {
9
+ expect(normalizeToPubkey("nprofile1qyw8wumn8ghj7umpw3jkcmrfw3jju6r6wfjrzdpe9e3k7mf0qyf8wumn8ghj7mn0wd68yat99e3k7mf0qqszv6q4uryjzr06xfxxew34wwc5hmjfmfpqn229d72gfegsdn2q3fg5g7lja")).toEqual("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5");
10
+ });
11
+ it("should return hex pubkey", () => {
12
+ expect(normalizeToPubkey("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5")).toEqual("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5");
13
+ });
14
+ it("should throw on invalid hex pubkey", () => {
15
+ expect(() => {
16
+ normalizeToPubkey("5028372");
17
+ }).toThrow();
18
+ });
19
+ it("should throw on invalid string", () => {
20
+ expect(() => {
21
+ normalizeToPubkey("testing");
22
+ }).toThrow();
23
+ });
24
+ });
25
+ describe("normalizeToSecretKey", () => {
26
+ it("should get secret key from nsec", () => {
27
+ expect(bytesToHex(normalizeToSecretKey("nsec1xe7znq745x5n68566l32ru72aajz3pk2cys9lnf3tuexvkw0dldsj8v2lm"))).toEqual("367c2983d5a1a93d1e9ad7e2a1f3caef642886cac1205fcd315f326659cf6fdb");
28
+ });
29
+ it("should get secret key from raw hex", () => {
30
+ expect(bytesToHex(normalizeToSecretKey("367c2983d5a1a93d1e9ad7e2a1f3caef642886cac1205fcd315f326659cf6fdb"))).toEqual("367c2983d5a1a93d1e9ad7e2a1f3caef642886cac1205fcd315f326659cf6fdb");
31
+ });
32
+ it("should throw on invalid hex key", () => {
33
+ expect(() => {
34
+ normalizeToSecretKey("209573290");
35
+ }).toThrow();
36
+ });
37
+ it("should throw on npub", () => {
38
+ expect(() => {
39
+ normalizeToSecretKey("npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr");
40
+ }).toThrow();
41
+ });
42
+ });
@@ -1,5 +1,4 @@
1
- import { EventTemplate, NostrEvent } from "nostr-tools";
2
- export declare function getCachedValue<T extends unknown>(event: NostrEvent | EventTemplate, symbol: symbol): T | undefined;
3
- export declare function setCachedValue<T extends unknown>(event: NostrEvent | EventTemplate, symbol: symbol, value: T): void;
1
+ export declare function getCachedValue<T extends unknown>(event: any, symbol: symbol): T | undefined;
2
+ export declare function setCachedValue<T extends unknown>(event: any, symbol: symbol, value: T): void;
4
3
  /** Internal method used to cache computed values on events */
5
- export declare function getOrComputeCachedValue<T extends unknown>(event: NostrEvent | EventTemplate, symbol: symbol, compute: (event: NostrEvent | EventTemplate) => T): T;
4
+ export declare function getOrComputeCachedValue<T extends unknown>(event: any, symbol: symbol, compute: () => T): T;
@@ -10,7 +10,7 @@ export function getOrComputeCachedValue(event, symbol, compute) {
10
10
  return Reflect.get(event, symbol);
11
11
  }
12
12
  else {
13
- const value = compute(event);
13
+ const value = compute();
14
14
  Reflect.set(event, symbol, value);
15
15
  return value;
16
16
  }
@@ -0,0 +1,4 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { HiddenContentSigner } from "./hidden-content.js";
3
+ /** Returns the decrypted content of a direct message */
4
+ export declare function decryptDirectMessage(message: NostrEvent, signer: HiddenContentSigner): Promise<string>;
@@ -0,0 +1,5 @@
1
+ import { getHiddenContent, unlockHiddenContent } from "./hidden-content.js";
2
+ /** Returns the decrypted content of a direct message */
3
+ export async function decryptDirectMessage(message, signer) {
4
+ return getHiddenContent(message) || (await unlockHiddenContent(message, signer));
5
+ }
@@ -1,4 +1,5 @@
1
- import { EventTemplate, NostrEvent, VerifiedEvent } from "nostr-tools";
1
+ import { NostrEvent, VerifiedEvent } from "nostr-tools";
2
+ import { IEventStore } from "../event-store/interface.js";
2
3
  export declare const EventUIDSymbol: unique symbol;
3
4
  export declare const EventIndexableTagsSymbol: unique symbol;
4
5
  export declare const FromCacheSymbol: unique symbol;
@@ -34,13 +35,20 @@ export declare function getIndexableTags(event: NostrEvent): Set<string>;
34
35
  * Returns the second index ( tag[1] ) of the first tag that matches the name
35
36
  * If the event has any hidden tags they will be searched first
36
37
  */
37
- export declare function getTagValue(event: NostrEvent | EventTemplate, name: string): string | undefined;
38
+ export declare function getTagValue<T extends {
39
+ kind: number;
40
+ tags: string[][];
41
+ content: string;
42
+ pubkey: string;
43
+ }>(event: T, name: string): string | undefined;
38
44
  /** Sets events verified flag without checking anything */
39
45
  export declare function fakeVerifyEvent(event: NostrEvent): event is VerifiedEvent;
40
46
  /** Marks an event as being from a cache */
41
47
  export declare function markFromCache(event: NostrEvent): void;
42
48
  /** Returns if an event was from a cache */
43
49
  export declare function isFromCache(event: NostrEvent): boolean;
50
+ /** Returns the EventStore of an event if its been added to one */
51
+ export declare function getParentEventStore<T extends object>(event: T): IEventStore | undefined;
44
52
  /**
45
53
  * Returns the replaceable identifier for a replaceable event
46
54
  * @throws
@@ -3,6 +3,7 @@ import { INDEXABLE_TAGS } from "../event-store/common.js";
3
3
  import { getHiddenTags } from "./hidden-tags.js";
4
4
  import { getOrComputeCachedValue } from "./cache.js";
5
5
  import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
6
+ import { EventStoreSymbol } from "../event-store/event-store.js";
6
7
  export const EventUIDSymbol = Symbol.for("event-uid");
7
8
  export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
8
9
  export const FromCacheSymbol = Symbol.for("from-cache");
@@ -90,6 +91,10 @@ export function markFromCache(event) {
90
91
  export function isFromCache(event) {
91
92
  return !!event[FromCacheSymbol];
92
93
  }
94
+ /** Returns the EventStore of an event if its been added to one */
95
+ export function getParentEventStore(event) {
96
+ return Reflect.get(event, EventStoreSymbol);
97
+ }
93
98
  /**
94
99
  * Returns the replaceable identifier for a replaceable event
95
100
  * @throws
@@ -0,0 +1,12 @@
1
+ import { NostrEvent, UnsignedEvent } from "nostr-tools";
2
+ import { HiddenContentSigner } from "./hidden-content.js";
3
+ export declare const GiftWrapSealSymbol: unique symbol;
4
+ export declare const GiftWrapEventSymbol: unique symbol;
5
+ /** Returns the unsigned seal event in a gift-wrap event */
6
+ export declare function getGiftWrapSeal(gift: NostrEvent): NostrEvent | undefined;
7
+ /** Returns the unsigned event in the gift-wrap seal */
8
+ export declare function getGiftWrapEvent(gift: NostrEvent): UnsignedEvent | undefined;
9
+ /** Returns if a gift-wrap event or gift-wrap seal is locked */
10
+ export declare function isGiftWrapLocked(gift: NostrEvent): boolean;
11
+ /** Unlocks and returns the unsigned seal event in a gift-wrap */
12
+ export declare function unlockGiftWrap(gift: NostrEvent, signer: HiddenContentSigner): Promise<UnsignedEvent>;