applesauce-core 3.1.0 → 4.1.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 +136 -0
  2. package/dist/event-store/async-event-store.js +364 -0
  3. package/dist/event-store/{event-set.d.ts → event-memory.d.ts} +17 -25
  4. package/dist/event-store/{event-set.js → event-memory.js} +54 -53
  5. package/dist/event-store/event-store.d.ts +59 -63
  6. package/dist/event-store/event-store.js +126 -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 +115 -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 +3 -1
  54. package/dist/helpers/pointers.js +4 -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 +17 -0
  58. package/dist/helpers/relay-selection.js +102 -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 +18 -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 +9 -0
  85. package/dist/observable/relay-selection.js +43 -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,118 @@ 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;
249
+ }
250
+ /** Remove multiple events that match the given filters */
251
+ removeByFilters(filters) {
252
+ // Get events that will be removed for notification
253
+ const eventsToRemove = this.getByFilters(filters);
254
+ // Remove from memory if available
255
+ if (this.memory)
256
+ this.memory.removeByFilters(filters);
257
+ // Remove from database
258
+ const removedCount = this.database.removeByFilters(filters);
259
+ // Notify subscriptions for each removed event
260
+ for (const event of eventsToRemove) {
261
+ this.remove$.next(event);
262
+ }
263
+ return removedCount;
236
264
  }
237
265
  /** Add an event to the store and notifies all subscribes it has updated */
238
266
  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);
267
+ // Map the event to the current instance in the database
268
+ const e = this.database.add(event);
269
+ if (!e)
270
+ return false;
271
+ // Notify the database that the event has updated
272
+ this.database.update?.(event);
273
+ this.update$.next(event);
274
+ return true;
244
275
  }
245
276
  /** Check if the store has an event by id */
246
277
  hasEvent(id) {
247
- return this.database.hasEvent(id);
278
+ // Check if the event exists in memory first, then in the database
279
+ return this.memory?.hasEvent(id) || this.database.hasEvent(id);
248
280
  }
249
281
  /** Get an event by id from the store */
250
282
  getEvent(id) {
251
- return this.database.getEvent(id);
283
+ // Get the event from memory first, then from the database
284
+ return this.memory?.getEvent(id) ?? this.mapToMemory(this.database.getEvent(id));
252
285
  }
253
286
  /** Check if the store has a replaceable event */
254
287
  hasReplaceable(kind, pubkey, d) {
255
- return this.database.hasReplaceable(kind, pubkey, d);
288
+ // Check if the event exists in memory first, then in the database
289
+ return this.memory?.hasReplaceable(kind, pubkey, d) || this.database.hasReplaceable(kind, pubkey, d);
256
290
  }
257
291
  /** Gets the latest version of a replaceable event */
258
292
  getReplaceable(kind, pubkey, identifier) {
259
- return this.database.getReplaceable(kind, pubkey, identifier);
293
+ // Get the event from memory first, then from the database
294
+ return (this.memory?.getReplaceable(kind, pubkey, identifier) ??
295
+ this.mapToMemory(this.database.getReplaceable(kind, pubkey, identifier)));
260
296
  }
261
297
  /** Returns all versions of a replaceable event */
262
298
  getReplaceableHistory(kind, pubkey, identifier) {
263
- return this.database.getReplaceableHistory(kind, pubkey, identifier);
299
+ // Get the events from memory first, then from the database
300
+ return (this.memory?.getReplaceableHistory(kind, pubkey, identifier) ??
301
+ this.database.getReplaceableHistory(kind, pubkey, identifier)?.map((e) => this.mapToMemory(e) ?? e));
264
302
  }
265
303
  /** Get all events matching a filter */
266
304
  getByFilters(filters) {
267
- return this.database.getByFilters(filters);
305
+ // NOTE: no way to read from memory since memory won't have the full set of events
306
+ const events = this.database.getByFilters(filters);
307
+ // Map events to memory if available for better performance
308
+ if (this.memory)
309
+ return events.map((e) => this.mapToMemory(e));
310
+ else
311
+ return events;
268
312
  }
269
313
  /** Returns a timeline of events that match filters */
270
314
  getTimeline(filters) {
271
- return this.database.getTimeline(filters);
315
+ const events = this.database.getTimeline(filters);
316
+ if (this.memory)
317
+ return events.map((e) => this.mapToMemory(e));
318
+ else
319
+ return events;
320
+ }
321
+ /** Passthrough method for the database.touch */
322
+ touch(event) {
323
+ return this.memory?.touch(event);
272
324
  }
273
325
  /** Sets the claim on the event and touches it */
274
326
  claim(event, claim) {
275
- this.database.claim(event, claim);
327
+ return this.memory?.claim(event, claim);
276
328
  }
277
329
  /** Checks if an event is claimed by anything */
278
330
  isClaimed(event) {
279
- return this.database.isClaimed(event);
331
+ return this.memory?.isClaimed(event) ?? false;
280
332
  }
281
333
  /** Removes a claim from an event */
282
334
  removeClaim(event, claim) {
283
- this.database.removeClaim(event, claim);
335
+ return this.memory?.removeClaim(event, claim);
284
336
  }
285
337
  /** Removes all claims on an event */
286
338
  clearClaim(event) {
287
- this.database.clearClaim(event);
339
+ return this.memory?.clearClaim(event);
288
340
  }
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;
341
+ /** Pass through method for the database.unclaimed */
342
+ unclaimed() {
343
+ return this.memory?.unclaimed() || (function* () { })();
322
344
  }
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))));
345
+ /** Removes any event that is not being used by a subscription */
346
+ prune(limit) {
347
+ return this.memory?.prune(limit) ?? 0;
335
348
  }
336
349
  /** Returns an observable that completes when an event is removed */
337
350
  removed(id) {
@@ -348,83 +361,6 @@ export class EventStore {
348
361
  }
349
362
  /** Creates an observable that emits when event is updated */
350
363
  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);
364
+ return this.update$.pipe(filter((e) => e.id === event || e === event));
429
365
  }
430
366
  }
@@ -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";