applesauce-core 3.1.0 → 4.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 (88) hide show
  1. package/dist/event-store/async-event-store.d.ts +134 -0
  2. package/dist/event-store/async-event-store.js +349 -0
  3. package/dist/event-store/{event-set.d.ts → event-memory.d.ts} +15 -25
  4. package/dist/event-store/{event-set.js → event-memory.js} +43 -53
  5. package/dist/event-store/event-store.d.ts +57 -63
  6. package/dist/event-store/event-store.js +111 -190
  7. package/dist/event-store/index.d.ts +2 -1
  8. package/dist/event-store/index.js +2 -1
  9. package/dist/event-store/interface.d.ts +111 -38
  10. package/dist/event-store/model-mixin.d.ts +59 -0
  11. package/dist/event-store/model-mixin.js +147 -0
  12. package/dist/helpers/app-data.d.ts +39 -0
  13. package/dist/helpers/app-data.js +68 -0
  14. package/dist/helpers/bookmarks.d.ts +11 -1
  15. package/dist/helpers/bookmarks.js +29 -4
  16. package/dist/helpers/comment.d.ts +13 -20
  17. package/dist/helpers/comment.js +16 -27
  18. package/dist/helpers/contacts.d.ts +10 -1
  19. package/dist/helpers/contacts.js +30 -3
  20. package/dist/helpers/encrypted-content-cache.js +7 -7
  21. package/dist/helpers/encrypted-content.d.ts +9 -2
  22. package/dist/helpers/encrypted-content.js +12 -9
  23. package/dist/helpers/event-cache.d.ts +3 -1
  24. package/dist/helpers/event-cache.js +3 -1
  25. package/dist/helpers/event-tags.d.ts +6 -0
  26. package/dist/helpers/event-tags.js +4 -0
  27. package/dist/helpers/event.d.ts +8 -1
  28. package/dist/helpers/event.js +6 -0
  29. package/dist/helpers/file-metadata.d.ts +4 -9
  30. package/dist/helpers/file-metadata.js +2 -10
  31. package/dist/helpers/filter.d.ts +4 -3
  32. package/dist/helpers/filter.js +3 -3
  33. package/dist/helpers/gift-wraps.d.ts +35 -14
  34. package/dist/helpers/gift-wraps.js +59 -50
  35. package/dist/helpers/groups.d.ts +2 -5
  36. package/dist/helpers/groups.js +2 -5
  37. package/dist/helpers/hidden-content.d.ts +14 -5
  38. package/dist/helpers/hidden-content.js +19 -8
  39. package/dist/helpers/hidden-tags.d.ts +16 -7
  40. package/dist/helpers/hidden-tags.js +47 -26
  41. package/dist/helpers/index.d.ts +1 -0
  42. package/dist/helpers/index.js +1 -0
  43. package/dist/helpers/legacy-messages.d.ts +17 -13
  44. package/dist/helpers/legacy-messages.js +21 -19
  45. package/dist/helpers/lists.js +2 -1
  46. package/dist/helpers/lnurl.d.ts +4 -0
  47. package/dist/helpers/lnurl.js +7 -3
  48. package/dist/helpers/mailboxes.d.ts +2 -6
  49. package/dist/helpers/mailboxes.js +26 -20
  50. package/dist/helpers/mutes.d.ts +11 -1
  51. package/dist/helpers/mutes.js +30 -5
  52. package/dist/helpers/picture-post.d.ts +2 -1
  53. package/dist/helpers/pointers.d.ts +1 -1
  54. package/dist/helpers/pointers.js +2 -1
  55. package/dist/helpers/profile.d.ts +7 -3
  56. package/dist/helpers/profile.js +7 -8
  57. package/dist/helpers/relay-selection.d.ts +13 -0
  58. package/dist/helpers/relay-selection.js +84 -0
  59. package/dist/helpers/relays.d.ts +3 -1
  60. package/dist/helpers/relays.js +18 -2
  61. package/dist/helpers/url.js +3 -3
  62. package/dist/helpers/wrapped-messages.d.ts +5 -3
  63. package/dist/helpers/wrapped-messages.js +5 -3
  64. package/dist/helpers/zap.d.ts +16 -14
  65. package/dist/helpers/zap.js +26 -28
  66. package/dist/models/common.d.ts +4 -4
  67. package/dist/models/common.js +79 -40
  68. package/dist/models/gift-wrap.d.ts +1 -1
  69. package/dist/models/gift-wrap.js +4 -4
  70. package/dist/models/index.d.ts +1 -0
  71. package/dist/models/index.js +1 -0
  72. package/dist/models/legacy-messages.d.ts +2 -2
  73. package/dist/models/legacy-messages.js +13 -10
  74. package/dist/models/outbox.d.ts +13 -0
  75. package/dist/models/outbox.js +18 -0
  76. package/dist/models/profile.d.ts +1 -1
  77. package/dist/models/zaps.d.ts +5 -4
  78. package/dist/models/zaps.js +2 -2
  79. package/dist/observable/index.d.ts +4 -3
  80. package/dist/observable/index.js +5 -4
  81. package/dist/observable/map-events-to-store.d.ts +5 -3
  82. package/dist/observable/map-events-to-store.js +14 -3
  83. package/dist/observable/map-events-to-timeline.js +12 -0
  84. package/dist/observable/relay-selection.d.ts +7 -0
  85. package/dist/observable/relay-selection.js +38 -0
  86. package/package.json +5 -3
  87. package/dist/observable/map-events-timeline.js +0 -9
  88. /package/dist/observable/{map-events-timeline.d.ts → map-events-to-timeline.d.ts} +0 -0
@@ -1,26 +1,20 @@
1
1
  import { kinds } from "nostr-tools";
2
2
  import { isAddressableKind } from "nostr-tools/kinds";
3
- import { EMPTY, filter, finalize, from, merge, mergeMap, ReplaySubject, share, take, timer } from "rxjs";
4
- import hash_sum from "hash-sum";
3
+ import { EMPTY, filter, mergeMap, Subject, take } from "rxjs";
5
4
  import { getDeleteCoordinates, getDeleteIds } from "../helpers/delete.js";
6
5
  import { createReplaceableAddress, EventStoreSymbol, FromCacheSymbol, isReplaceable } from "../helpers/event.js";
7
6
  import { getExpirationTimestamp } from "../helpers/expiration.js";
8
- import { matchFilters } from "../helpers/filter.js";
9
7
  import { parseCoordinate } from "../helpers/pointers.js";
10
8
  import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
11
9
  import { unixNow } from "../helpers/time.js";
12
- import { UserBlossomServersModel } from "../models/blossom.js";
13
- import { EventModel, EventsModel, ReplaceableModel, ReplaceableSetModel, TimelineModel } from "../models/common.js";
14
- import { ContactsModel } from "../models/contacts.js";
15
- import { CommentsModel, ThreadModel } from "../models/index.js";
16
- import { MailboxesModel } from "../models/mailboxes.js";
17
- import { MuteModel } from "../models/mutes.js";
18
- import { ProfileModel } from "../models/profile.js";
19
- import { ReactionsModel } from "../models/reactions.js";
20
- import { EventSet } from "./event-set.js";
21
- /** An extended {@link EventSet} that handles replaceable events, delets, and models */
22
- export class EventStore {
10
+ import { EventMemory } from "./event-memory.js";
11
+ import { EventStoreModelMixin } from "./model-mixin.js";
12
+ /** A wrapper around an event database that handles replaceable events, deletes, and models */
13
+ export class EventStore extends EventStoreModelMixin(class {
14
+ }) {
23
15
  database;
16
+ /** Optional memory database for ensuring single event instances */
17
+ memory;
24
18
  /** Enable this to keep old versions of replaceable events */
25
19
  keepOldVersions = false;
26
20
  /** Enable this to keep expired events */
@@ -31,11 +25,11 @@ export class EventStore {
31
25
  */
32
26
  verifyEvent;
33
27
  /** A stream of new events added to the store */
34
- insert$;
28
+ insert$ = new Subject();
35
29
  /** A stream of events that have been updated */
36
- update$;
30
+ update$ = new Subject();
37
31
  /** A stream of events that have been removed */
38
- remove$;
32
+ remove$ = new Subject();
39
33
  /**
40
34
  * A method that will be called when an event isn't found in the store
41
35
  * @experimental
@@ -51,27 +45,31 @@ export class EventStore {
51
45
  * @experimental
52
46
  */
53
47
  addressableLoader;
54
- constructor() {
55
- this.database = new EventSet();
56
- // verify events before they are added to the database
57
- this.database.onBeforeInsert = (event) => {
58
- // Ignore events that are invalid
59
- if (this.verifyEvent && this.verifyEvent(event) === false)
60
- return false;
61
- else
62
- return true;
63
- };
48
+ constructor(database = new EventMemory()) {
49
+ super();
50
+ if (database) {
51
+ this.database = database;
52
+ this.memory = new EventMemory();
53
+ }
54
+ else {
55
+ // If no database is provided, its the same as having a memory database
56
+ this.database = this.memory = new EventMemory();
57
+ }
64
58
  // when events are added to the database, add the symbol
65
- this.database.insert$.subscribe((event) => {
59
+ this.insert$.subscribe((event) => {
66
60
  Reflect.set(event, EventStoreSymbol, this);
67
61
  });
68
62
  // when events are removed from the database, remove the symbol
69
- this.database.remove$.subscribe((event) => {
63
+ this.remove$.subscribe((event) => {
70
64
  Reflect.deleteProperty(event, EventStoreSymbol);
71
65
  });
72
- this.insert$ = this.database.insert$;
73
- this.update$ = this.database.update$;
74
- this.remove$ = this.database.remove$;
66
+ }
67
+ mapToMemory(event) {
68
+ if (event === undefined)
69
+ return undefined;
70
+ if (!this.memory)
71
+ return event;
72
+ return this.memory.add(event);
75
73
  }
76
74
  // delete state
77
75
  deletedIds = new Set();
@@ -137,9 +135,7 @@ export class EventStore {
137
135
  for (const id of ids) {
138
136
  this.deletedIds.add(id);
139
137
  // remove deleted events in the database
140
- const event = this.database.getEvent(id);
141
- if (event)
142
- this.database.remove(event);
138
+ this.remove(id);
143
139
  }
144
140
  const coords = getDeleteCoordinates(deleteEvent);
145
141
  for (const coord of coords) {
@@ -152,7 +148,7 @@ export class EventStore {
152
148
  const events = this.database.getReplaceableHistory(parsed.kind, parsed.pubkey, parsed.identifier) ?? [];
153
149
  for (const event of events) {
154
150
  if (event.created_at < deleteEvent.created_at)
155
- this.database.remove(event);
151
+ this.remove(event);
156
152
  }
157
153
  }
158
154
  }
@@ -173,6 +169,7 @@ export class EventStore {
173
169
  * @returns The existing event or the event that was added, if it was ignored returns null
174
170
  */
175
171
  add(event, fromRelay) {
172
+ // Handle delete events differently
176
173
  if (event.kind === kinds.EventDeletion)
177
174
  this.handleDeleteEvent(event);
178
175
  // Ignore if the event was deleted
@@ -193,32 +190,38 @@ export class EventStore {
193
190
  return existing[0];
194
191
  }
195
192
  }
196
- else if (this.database.hasEvent(event.id)) {
197
- // Duplicate event, copy symbols and return existing event
198
- const existing = this.database.getEvent(event.id);
199
- if (existing) {
200
- EventStore.mergeDuplicateEvent(event, existing);
201
- return existing;
202
- }
193
+ // Verify event before inserting into the database
194
+ if (this.verifyEvent && this.verifyEvent(event) === false)
195
+ return null;
196
+ // Always add event to memory
197
+ const existing = this.memory?.add(event);
198
+ // If the memory returned a different instance, this is a duplicate event
199
+ if (existing && existing !== event) {
200
+ // Copy cached symbols and return existing event
201
+ EventStore.mergeDuplicateEvent(event, existing);
202
+ // attach relay this event was from
203
+ if (fromRelay)
204
+ addSeenRelay(existing, fromRelay);
205
+ return existing;
203
206
  }
204
207
  // Insert event into database
205
- const inserted = this.database.add(event);
206
- // If the event was ignored, return null
207
- if (inserted === null)
208
- return null;
208
+ const inserted = this.mapToMemory(this.database.add(event));
209
209
  // Copy cached data if its a duplicate event
210
210
  if (event !== inserted)
211
211
  EventStore.mergeDuplicateEvent(event, inserted);
212
212
  // attach relay this event was from
213
213
  if (fromRelay)
214
214
  addSeenRelay(inserted, fromRelay);
215
+ // Emit insert$ signal
216
+ if (inserted === event)
217
+ this.insert$.next(inserted);
215
218
  // remove all old version of the replaceable event
216
219
  if (!this.keepOldVersions && isReplaceable(event.kind)) {
217
220
  const existing = this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
218
- if (existing) {
221
+ if (existing && existing.length > 0) {
219
222
  const older = Array.from(existing).filter((e) => e.created_at < event.created_at);
220
223
  for (const old of older)
221
- this.database.remove(old);
224
+ this.remove(old);
222
225
  // return the newest version of the replaceable event
223
226
  // most of the time this will be === event, but not always
224
227
  if (existing.length !== older.length)
@@ -230,108 +233,103 @@ export class EventStore {
230
233
  this.handleExpiringEvent(inserted);
231
234
  return inserted;
232
235
  }
233
- /** Removes an event from the database and updates subscriptions */
236
+ /** Removes an event from the store and updates subscriptions */
234
237
  remove(event) {
235
- return this.database.remove(event);
238
+ let instance = this.memory?.getEvent(typeof event === "string" ? event : event.id);
239
+ // Remove from memory if available
240
+ if (this.memory)
241
+ this.memory.remove(event);
242
+ // Remove the event from the database
243
+ const removed = this.database.remove(event);
244
+ // If the event was removed, notify the subscriptions
245
+ if (removed && instance) {
246
+ this.remove$.next(instance);
247
+ }
248
+ return removed;
236
249
  }
237
250
  /** Add an event to the store and notifies all subscribes it has updated */
238
251
  update(event) {
239
- return this.database.update(event);
240
- }
241
- /** Removes any event that is not being used by a subscription */
242
- prune(max) {
243
- return this.database.prune(max);
252
+ // Map the event to the current instance in the database
253
+ const e = this.database.add(event);
254
+ if (!e)
255
+ return false;
256
+ // Notify the database that the event has updated
257
+ this.database.update?.(event);
258
+ this.update$.next(event);
259
+ return true;
244
260
  }
245
261
  /** Check if the store has an event by id */
246
262
  hasEvent(id) {
247
- return this.database.hasEvent(id);
263
+ // Check if the event exists in memory first, then in the database
264
+ return this.memory?.hasEvent(id) || this.database.hasEvent(id);
248
265
  }
249
266
  /** Get an event by id from the store */
250
267
  getEvent(id) {
251
- return this.database.getEvent(id);
268
+ // Get the event from memory first, then from the database
269
+ return this.memory?.getEvent(id) ?? this.mapToMemory(this.database.getEvent(id));
252
270
  }
253
271
  /** Check if the store has a replaceable event */
254
272
  hasReplaceable(kind, pubkey, d) {
255
- return this.database.hasReplaceable(kind, pubkey, d);
273
+ // Check if the event exists in memory first, then in the database
274
+ return this.memory?.hasReplaceable(kind, pubkey, d) || this.database.hasReplaceable(kind, pubkey, d);
256
275
  }
257
276
  /** Gets the latest version of a replaceable event */
258
277
  getReplaceable(kind, pubkey, identifier) {
259
- return this.database.getReplaceable(kind, pubkey, identifier);
278
+ // Get the event from memory first, then from the database
279
+ return (this.memory?.getReplaceable(kind, pubkey, identifier) ??
280
+ this.mapToMemory(this.database.getReplaceable(kind, pubkey, identifier)));
260
281
  }
261
282
  /** Returns all versions of a replaceable event */
262
283
  getReplaceableHistory(kind, pubkey, identifier) {
263
- return this.database.getReplaceableHistory(kind, pubkey, identifier);
284
+ // Get the events from memory first, then from the database
285
+ return (this.memory?.getReplaceableHistory(kind, pubkey, identifier) ??
286
+ this.database.getReplaceableHistory(kind, pubkey, identifier)?.map((e) => this.mapToMemory(e) ?? e));
264
287
  }
265
288
  /** Get all events matching a filter */
266
289
  getByFilters(filters) {
267
- return this.database.getByFilters(filters);
290
+ // NOTE: no way to read from memory since memory won't have the full set of events
291
+ const events = this.database.getByFilters(filters);
292
+ // Map events to memory if available for better performance
293
+ if (this.memory)
294
+ return events.map((e) => this.mapToMemory(e));
295
+ else
296
+ return events;
268
297
  }
269
298
  /** Returns a timeline of events that match filters */
270
299
  getTimeline(filters) {
271
- return this.database.getTimeline(filters);
300
+ const events = this.database.getTimeline(filters);
301
+ if (this.memory)
302
+ return events.map((e) => this.mapToMemory(e));
303
+ else
304
+ return events;
305
+ }
306
+ /** Passthrough method for the database.touch */
307
+ touch(event) {
308
+ return this.memory?.touch(event);
272
309
  }
273
310
  /** Sets the claim on the event and touches it */
274
311
  claim(event, claim) {
275
- this.database.claim(event, claim);
312
+ return this.memory?.claim(event, claim);
276
313
  }
277
314
  /** Checks if an event is claimed by anything */
278
315
  isClaimed(event) {
279
- return this.database.isClaimed(event);
316
+ return this.memory?.isClaimed(event) ?? false;
280
317
  }
281
318
  /** Removes a claim from an event */
282
319
  removeClaim(event, claim) {
283
- this.database.removeClaim(event, claim);
320
+ return this.memory?.removeClaim(event, claim);
284
321
  }
285
322
  /** Removes all claims on an event */
286
323
  clearClaim(event) {
287
- this.database.clearClaim(event);
324
+ return this.memory?.clearClaim(event);
288
325
  }
289
- /** A directory of all active models */
290
- models = new Map();
291
- /** How long a model should be kept "warm" while nothing is subscribed to it */
292
- modelKeepWarm = 60_000;
293
- /** Get or create a model on the event store */
294
- model(constructor, ...args) {
295
- let models = this.models.get(constructor);
296
- if (!models) {
297
- models = new Map();
298
- this.models.set(constructor, models);
299
- }
300
- const key = constructor.getKey ? constructor.getKey(...args) : hash_sum(args);
301
- let model = models.get(key);
302
- // Create the model if it does not exist
303
- if (!model) {
304
- const cleanup = () => {
305
- // Remove the model from the cache if its the same one
306
- if (models.get(key) === model)
307
- models.delete(key);
308
- };
309
- model = constructor(...args)(this).pipe(
310
- // remove the model when its unsubscribed
311
- finalize(cleanup),
312
- // only subscribe to models once for all subscriptions
313
- share({
314
- connector: () => new ReplaySubject(1),
315
- resetOnComplete: () => timer(this.modelKeepWarm),
316
- resetOnRefCountZero: () => timer(this.modelKeepWarm),
317
- }));
318
- // Add the model to the cache
319
- models.set(key, model);
320
- }
321
- return model;
326
+ /** Pass through method for the database.unclaimed */
327
+ unclaimed() {
328
+ return this.memory?.unclaimed() || (function* () { })();
322
329
  }
323
- /**
324
- * Creates an observable that streams all events that match the filter
325
- * @param filters
326
- * @param [onlyNew=false] Only subscribe to new events
327
- */
328
- filters(filters, onlyNew = false) {
329
- filters = Array.isArray(filters) ? filters : [filters];
330
- return merge(
331
- // merge existing events
332
- onlyNew ? EMPTY : from(this.getByFilters(filters)),
333
- // subscribe to future events
334
- this.insert$.pipe(filter((e) => matchFilters(filters, e))));
330
+ /** Removes any event that is not being used by a subscription */
331
+ prune(limit) {
332
+ return this.memory?.prune(limit) ?? 0;
335
333
  }
336
334
  /** Returns an observable that completes when an event is removed */
337
335
  removed(id) {
@@ -348,83 +346,6 @@ export class EventStore {
348
346
  }
349
347
  /** Creates an observable that emits when event is updated */
350
348
  updated(event) {
351
- return this.database.update$.pipe(filter((e) => e.id === event || e === event));
352
- }
353
- // Helper methods for creating models
354
- /** Creates a {@link EventModel} */
355
- event(pointer) {
356
- if (typeof pointer === "string")
357
- pointer = { id: pointer };
358
- return this.model(EventModel, pointer);
359
- }
360
- replaceable(...args) {
361
- let pointer;
362
- // Parse arguments
363
- if (args.length === 1) {
364
- pointer = args[0];
365
- }
366
- else if (args.length === 3 || args.length === 2) {
367
- let [kind, pubkey, identifier] = args;
368
- pointer = { kind, pubkey, identifier };
369
- }
370
- if (!pointer)
371
- throw new Error("Invalid arguments, expected address pointer or kind, pubkey, identifier");
372
- return this.model(ReplaceableModel, pointer);
373
- }
374
- /** Subscribe to an addressable event by pointer */
375
- addressable(pointer) {
376
- return this.model(ReplaceableModel, pointer);
377
- }
378
- /** Creates a {@link TimelineModel} */
379
- timeline(filters, includeOldVersion = false) {
380
- return this.model(TimelineModel, filters, includeOldVersion);
381
- }
382
- /** Subscribe to a users profile */
383
- profile(user) {
384
- return this.model(ProfileModel, user);
385
- }
386
- /** Subscribe to a users contacts */
387
- contacts(user) {
388
- if (typeof user === "string")
389
- user = { pubkey: user };
390
- return this.model(ContactsModel, user);
391
- }
392
- /** Subscribe to a users mutes */
393
- mutes(user) {
394
- if (typeof user === "string")
395
- user = { pubkey: user };
396
- return this.model(MuteModel, user);
397
- }
398
- /** Subscribe to a users NIP-65 mailboxes */
399
- mailboxes(user) {
400
- if (typeof user === "string")
401
- user = { pubkey: user };
402
- return this.model(MailboxesModel, user);
403
- }
404
- /** Subscribe to a users blossom servers */
405
- blossomServers(user) {
406
- if (typeof user === "string")
407
- user = { pubkey: user };
408
- return this.model(UserBlossomServersModel, user);
409
- }
410
- /** Subscribe to an event's reactions */
411
- reactions(event) {
412
- return this.model(ReactionsModel, event);
413
- }
414
- /** Subscribe to a thread */
415
- thread(root) {
416
- return this.model(ThreadModel, root);
417
- }
418
- /** Subscribe to a event's comments */
419
- comments(event) {
420
- return this.model(CommentsModel, event);
421
- }
422
- /** @deprecated use multiple {@link EventModel} instead */
423
- events(ids) {
424
- return this.model(EventsModel, ids);
425
- }
426
- /** @deprecated use multiple {@link ReplaceableModel} instead */
427
- replaceableSet(pointers) {
428
- return this.model(ReplaceableSetModel, pointers);
349
+ return this.update$.pipe(filter((e) => e.id === event || e === event));
429
350
  }
430
351
  }
@@ -1,3 +1,4 @@
1
+ export * from "./async-event-store.js";
2
+ export * from "./event-memory.js";
1
3
  export * from "./event-store.js";
2
- export * from "./event-set.js";
3
4
  export * from "./interface.js";
@@ -1,3 +1,4 @@
1
+ export * from "./async-event-store.js";
2
+ export * from "./event-memory.js";
1
3
  export * from "./event-store.js";
2
- export * from "./event-set.js";
3
4
  export * from "./interface.js";
@@ -1,28 +1,44 @@
1
1
  import { Filter, NostrEvent } from "nostr-tools";
2
2
  import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
3
3
  import { Observable } from "rxjs";
4
- import { LRU } from "../helpers/lru.js";
5
- import { Mutes } from "../helpers/mutes.js";
4
+ import { AddressPointerWithoutD } from "../helpers/pointers.js";
6
5
  import { ProfileContent } from "../helpers/profile.js";
6
+ import { Mutes } from "../helpers/mutes.js";
7
7
  import { Thread } from "../models/thread.js";
8
- import { AddressPointerWithoutD } from "../helpers/pointers.js";
9
8
  /** The read interface for an event store */
10
9
  export interface IEventStoreRead {
11
10
  /** Check if the event store has an event with id */
12
11
  hasEvent(id: string): boolean;
13
- /** Check if the event store has a replaceable event */
14
- hasReplaceable(kind: number, pubkey: string, identifier?: string): boolean;
15
12
  /** Get an event by id */
16
13
  getEvent(id: string): NostrEvent | undefined;
14
+ /** Check if the event store has a replaceable event */
15
+ hasReplaceable(kind: number, pubkey: string, identifier?: string): boolean;
17
16
  /** Get a replaceable event */
18
17
  getReplaceable(kind: number, pubkey: string, identifier?: string): NostrEvent | undefined;
19
18
  /** Get the history of a replaceable event */
20
19
  getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[] | undefined;
21
20
  /** Get all events that match the filters */
22
- getByFilters(filters: Filter | Filter[]): Set<NostrEvent>;
21
+ getByFilters(filters: Filter | Filter[]): NostrEvent[];
23
22
  /** Get a timeline of events that match the filters */
24
23
  getTimeline(filters: Filter | Filter[]): NostrEvent[];
25
24
  }
25
+ /** The async read interface for an event store */
26
+ export interface IAsyncEventStoreRead {
27
+ /** Check if the event store has an event with id */
28
+ hasEvent(id: string): Promise<boolean>;
29
+ /** Get an event by id */
30
+ getEvent(id: string): Promise<NostrEvent | undefined>;
31
+ /** Check if the event store has a replaceable event */
32
+ hasReplaceable(kind: number, pubkey: string, identifier?: string): Promise<boolean>;
33
+ /** Get a replaceable event */
34
+ getReplaceable(kind: number, pubkey: string, identifier?: string): Promise<NostrEvent | undefined>;
35
+ /** Get the history of a replaceable event */
36
+ getReplaceableHistory(kind: number, pubkey: string, identifier?: string): Promise<NostrEvent[] | undefined>;
37
+ /** Get all events that match the filters */
38
+ getByFilters(filters: Filter | Filter[]): Promise<NostrEvent[]>;
39
+ /** Get a timeline of events that match the filters */
40
+ getTimeline(filters: Filter | Filter[]): Promise<NostrEvent[]>;
41
+ }
26
42
  /** The stream interface for an event store */
27
43
  export interface IEventStoreStreams {
28
44
  /** A stream of new events added to the store */
@@ -41,8 +57,19 @@ export interface IEventStoreActions {
41
57
  /** Notify the store that an event has updated */
42
58
  update(event: NostrEvent): void;
43
59
  }
60
+ /** The async actions for an event store */
61
+ export interface IAsyncEventStoreActions {
62
+ /** Add an event to the store */
63
+ add(event: NostrEvent): Promise<NostrEvent | null>;
64
+ /** Remove an event from the store */
65
+ remove(event: string | NostrEvent): Promise<boolean>;
66
+ /** Notify the store that an event has updated */
67
+ update(event: NostrEvent): Promise<void>;
68
+ }
44
69
  /** The claim interface for an event store */
45
70
  export interface IEventClaims {
71
+ /** Tell the store that this event was used */
72
+ touch(event: NostrEvent): void;
46
73
  /** Sets the claim on the event and touches it */
47
74
  claim(event: NostrEvent, claim: any): void;
48
75
  /** Checks if an event is claimed by anything */
@@ -51,59 +78,105 @@ export interface IEventClaims {
51
78
  removeClaim(event: NostrEvent, claim: any): void;
52
79
  /** Removes all claims on an event */
53
80
  clearClaim(event: NostrEvent): void;
81
+ /** Returns a generator of unclaimed events in order of least used */
82
+ unclaimed(): Generator<NostrEvent>;
54
83
  }
55
84
  /** An event store that can be subscribed to */
56
- export interface IEventStoreSubscriptions {
57
- /** Susbscribe to an event by id */
85
+ export interface IEventSubscriptions {
86
+ /** Subscribe to an event by id */
58
87
  event(id: string | EventPointer): Observable<NostrEvent | undefined>;
59
88
  /** Subscribe to a replaceable event by pointer */
60
89
  replaceable(pointer: AddressPointerWithoutD): Observable<NostrEvent | undefined>;
90
+ /** Subscribe to a replaceable event with legacy arguments */
91
+ replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
61
92
  /** Subscribe to an addressable event by pointer */
62
93
  addressable(pointer: AddressPointer): Observable<NostrEvent | undefined>;
63
94
  /** Subscribe to a batch of events that match the filters */
64
- filter(filters: Filter | Filter[]): Observable<NostrEvent[]>;
95
+ filters(filters: Filter | Filter[], onlyNew?: boolean): Observable<NostrEvent>;
96
+ /** Subscribe to a sorted timeline of events that match the filters */
97
+ timeline(filters: Filter | Filter[], onlyNew?: boolean): Observable<NostrEvent[]>;
98
+ }
99
+ /** @deprecated use {@link IEventSubscriptions} instead */
100
+ export interface IEventStoreSubscriptions extends IEventSubscriptions {
65
101
  }
66
102
  /** Methods for creating common models */
67
- export interface IEventStoreModels {
68
- model<T extends unknown, Args extends Array<any>>(constructor: ModelConstructor<T, Args>, ...args: Args): Observable<T>;
69
- event(id: string): Observable<NostrEvent | undefined>;
70
- replaceable(pointer: AddressPointerWithoutD): Observable<NostrEvent | undefined>;
71
- addressable(pointer: AddressPointer): Observable<NostrEvent | undefined>;
72
- timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
103
+ export interface IEventModelMixin<TStore extends IEventStore | IAsyncEventStore> {
104
+ model<T extends unknown, Args extends Array<any>>(constructor: ModelConstructor<T, Args, TStore>, ...args: Args): Observable<T>;
105
+ /** @deprecated use multiple {@link EventModel} instead */
73
106
  events(ids: string[]): Observable<Record<string, NostrEvent | undefined>>;
107
+ /** @deprecated use multiple {@link ReplaceableModel} instead */
74
108
  replaceableSet(pointers: (AddressPointer | AddressPointerWithoutD)[]): Observable<Record<string, NostrEvent | undefined>>;
75
109
  }
76
- /** A computed view of an event set or event store */
77
- export type Model<T extends unknown> = (events: IEventStore) => Observable<T>;
78
- /** A constructor for a {@link Model} */
79
- export type ModelConstructor<T extends unknown, Args extends Array<any>> = ((...args: Args) => Model<T>) & {
80
- getKey?: (...args: Args) => string;
81
- };
82
- /** The base interface for a set of events */
83
- export interface IEventSet extends IEventStoreRead, IEventStoreStreams, IEventStoreActions, IEventClaims {
84
- events: LRU<NostrEvent>;
85
- }
86
- export interface IEventStore extends IEventStoreRead, IEventStoreStreams, IEventStoreActions, IEventStoreModels, IEventClaims {
87
- /** Enable this to keep old versions of replaceable events */
88
- keepOldVersions: boolean;
89
- /** Enable this to keep expired events */
90
- keepExpired: boolean;
91
- filters(filters: Filter | Filter[]): Observable<NostrEvent>;
92
- updated(id: string | NostrEvent): Observable<NostrEvent>;
93
- removed(id: string): Observable<never>;
94
- replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
95
- replaceable(pointer: AddressPointerWithoutD): Observable<NostrEvent | undefined>;
96
- eventLoader?: (pointer: EventPointer) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
97
- replaceableLoader?: (pointer: AddressPointerWithoutD) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
98
- addressableLoader?: (pointer: AddressPointer) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
110
+ /** Methods for creating helpful models */
111
+ export interface IEventHelpfulSubscriptions {
112
+ /** Subscribe to a users profile */
99
113
  profile(user: string | ProfilePointer): Observable<ProfileContent | undefined>;
114
+ /** Subscribe to a users contacts */
100
115
  contacts(user: string | ProfilePointer): Observable<ProfilePointer[]>;
116
+ /** Subscribe to a users mutes */
101
117
  mutes(user: string | ProfilePointer): Observable<Mutes | undefined>;
118
+ /** Subscribe to a users NIP-65 mailboxes */
102
119
  mailboxes(user: string | ProfilePointer): Observable<{
103
120
  inboxes: string[];
104
121
  outboxes: string[];
105
122
  } | undefined>;
123
+ /** Subscribe to a users blossom servers */
106
124
  blossomServers(user: string | ProfilePointer): Observable<URL[]>;
125
+ /** Subscribe to an event's reactions */
107
126
  reactions(event: NostrEvent): Observable<NostrEvent[]>;
127
+ /** Subscribe to a thread */
108
128
  thread(root: string | EventPointer | AddressPointer): Observable<Thread>;
129
+ /** Subscribe to a event's comments */
130
+ comments(event: NostrEvent): Observable<NostrEvent[]>;
131
+ }
132
+ /** @deprecated use {@link IEventModelMixin} instead */
133
+ export interface IEventStoreModels extends IEventModelMixin<IEventStore> {
134
+ }
135
+ /** The interface that is passed to the model for creating subscriptions */
136
+ export type ModelEventStore<TStore extends IEventStore | IAsyncEventStore> = IEventStoreStreams & IEventSubscriptions & IEventModelMixin<TStore> & IEventFallbackLoaders & TStore;
137
+ /** A computed view of an event set or event store */
138
+ export type Model<T extends unknown, TStore extends IEventStore | IAsyncEventStore = IEventStore | IAsyncEventStore> = (events: ModelEventStore<TStore>) => Observable<T>;
139
+ /** A constructor for a {@link Model} */
140
+ export type ModelConstructor<T extends unknown, Args extends Array<any>, TStore extends IEventStore | IAsyncEventStore = IEventStore> = ((...args: Args) => Model<T, TStore>) & {
141
+ getKey?: (...args: Args) => string;
142
+ };
143
+ /** The base interface for a database of events */
144
+ export interface IEventDatabase extends IEventStoreRead {
145
+ /** Add an event to the database */
146
+ add(event: NostrEvent): NostrEvent;
147
+ /** Remove an event from the database */
148
+ remove(event: string | NostrEvent): boolean;
149
+ /** Notifies the database that an event has updated */
150
+ update?: (event: NostrEvent) => void;
151
+ }
152
+ /** The async base interface for a set of events */
153
+ export interface IAsyncEventDatabase extends IAsyncEventStoreRead {
154
+ /** Add an event to the database */
155
+ add(event: NostrEvent): Promise<NostrEvent>;
156
+ /** Remove an event from the database */
157
+ remove(event: string | NostrEvent): Promise<boolean>;
158
+ /** Notifies the database that an event has updated */
159
+ update?: (event: NostrEvent) => void;
160
+ }
161
+ /** The base interface for the in-memory database of events */
162
+ export interface IEventMemory extends IEventStoreRead, IEventClaims {
163
+ /** Add an event to the store */
164
+ add(event: NostrEvent): NostrEvent;
165
+ /** Remove an event from the store */
166
+ remove(event: string | NostrEvent): boolean;
167
+ }
168
+ /** A set of methods that an event store will use to load single events it does not have */
169
+ export interface IEventFallbackLoaders {
170
+ /** A method that will be called when an event isn't found in the store */
171
+ eventLoader?: (pointer: EventPointer) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
172
+ /** A method that will be called when a replaceable event isn't found in the store */
173
+ replaceableLoader?: (pointer: AddressPointerWithoutD) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
174
+ /** A method that will be called when an addressable event isn't found in the store */
175
+ addressableLoader?: (pointer: AddressPointer) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
176
+ }
177
+ /** The async event store interface */
178
+ export interface IAsyncEventStore extends IAsyncEventStoreRead, IEventStoreStreams, IEventSubscriptions, IAsyncEventStoreActions, IEventModelMixin<IAsyncEventStore>, IEventHelpfulSubscriptions, IEventClaims, IEventFallbackLoaders {
179
+ }
180
+ /** The sync event store interface */
181
+ export interface IEventStore extends IEventStoreRead, IEventStoreStreams, IEventSubscriptions, IEventStoreActions, IEventModelMixin<IEventStore>, IEventHelpfulSubscriptions, IEventClaims, IEventFallbackLoaders {
109
182
  }