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.
- package/dist/event-store/async-event-store.d.ts +134 -0
- package/dist/event-store/async-event-store.js +349 -0
- package/dist/event-store/{event-set.d.ts → event-memory.d.ts} +15 -25
- package/dist/event-store/{event-set.js → event-memory.js} +43 -53
- package/dist/event-store/event-store.d.ts +57 -63
- package/dist/event-store/event-store.js +111 -190
- package/dist/event-store/index.d.ts +2 -1
- package/dist/event-store/index.js +2 -1
- package/dist/event-store/interface.d.ts +111 -38
- package/dist/event-store/model-mixin.d.ts +59 -0
- package/dist/event-store/model-mixin.js +147 -0
- package/dist/helpers/app-data.d.ts +39 -0
- package/dist/helpers/app-data.js +68 -0
- package/dist/helpers/bookmarks.d.ts +11 -1
- package/dist/helpers/bookmarks.js +29 -4
- package/dist/helpers/comment.d.ts +13 -20
- package/dist/helpers/comment.js +16 -27
- package/dist/helpers/contacts.d.ts +10 -1
- package/dist/helpers/contacts.js +30 -3
- package/dist/helpers/encrypted-content-cache.js +7 -7
- package/dist/helpers/encrypted-content.d.ts +9 -2
- package/dist/helpers/encrypted-content.js +12 -9
- package/dist/helpers/event-cache.d.ts +3 -1
- package/dist/helpers/event-cache.js +3 -1
- package/dist/helpers/event-tags.d.ts +6 -0
- package/dist/helpers/event-tags.js +4 -0
- package/dist/helpers/event.d.ts +8 -1
- package/dist/helpers/event.js +6 -0
- package/dist/helpers/file-metadata.d.ts +4 -9
- package/dist/helpers/file-metadata.js +2 -10
- package/dist/helpers/filter.d.ts +4 -3
- package/dist/helpers/filter.js +3 -3
- package/dist/helpers/gift-wraps.d.ts +35 -14
- package/dist/helpers/gift-wraps.js +59 -50
- package/dist/helpers/groups.d.ts +2 -5
- package/dist/helpers/groups.js +2 -5
- package/dist/helpers/hidden-content.d.ts +14 -5
- package/dist/helpers/hidden-content.js +19 -8
- package/dist/helpers/hidden-tags.d.ts +16 -7
- package/dist/helpers/hidden-tags.js +47 -26
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/legacy-messages.d.ts +17 -13
- package/dist/helpers/legacy-messages.js +21 -19
- package/dist/helpers/lists.js +2 -1
- package/dist/helpers/lnurl.d.ts +4 -0
- package/dist/helpers/lnurl.js +7 -3
- package/dist/helpers/mailboxes.d.ts +2 -6
- package/dist/helpers/mailboxes.js +26 -20
- package/dist/helpers/mutes.d.ts +11 -1
- package/dist/helpers/mutes.js +30 -5
- package/dist/helpers/picture-post.d.ts +2 -1
- package/dist/helpers/pointers.d.ts +1 -1
- package/dist/helpers/pointers.js +2 -1
- package/dist/helpers/profile.d.ts +7 -3
- package/dist/helpers/profile.js +7 -8
- package/dist/helpers/relay-selection.d.ts +13 -0
- package/dist/helpers/relay-selection.js +84 -0
- package/dist/helpers/relays.d.ts +3 -1
- package/dist/helpers/relays.js +18 -2
- package/dist/helpers/url.js +3 -3
- package/dist/helpers/wrapped-messages.d.ts +5 -3
- package/dist/helpers/wrapped-messages.js +5 -3
- package/dist/helpers/zap.d.ts +16 -14
- package/dist/helpers/zap.js +26 -28
- package/dist/models/common.d.ts +4 -4
- package/dist/models/common.js +79 -40
- package/dist/models/gift-wrap.d.ts +1 -1
- package/dist/models/gift-wrap.js +4 -4
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/models/legacy-messages.d.ts +2 -2
- package/dist/models/legacy-messages.js +13 -10
- package/dist/models/outbox.d.ts +13 -0
- package/dist/models/outbox.js +18 -0
- package/dist/models/profile.d.ts +1 -1
- package/dist/models/zaps.d.ts +5 -4
- package/dist/models/zaps.js +2 -2
- package/dist/observable/index.d.ts +4 -3
- package/dist/observable/index.js +5 -4
- package/dist/observable/map-events-to-store.d.ts +5 -3
- package/dist/observable/map-events-to-store.js +14 -3
- package/dist/observable/map-events-to-timeline.js +12 -0
- package/dist/observable/relay-selection.d.ts +7 -0
- package/dist/observable/relay-selection.js +38 -0
- package/package.json +5 -3
- package/dist/observable/map-events-timeline.js +0 -9
- /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,
|
|
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 {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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.
|
|
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.
|
|
63
|
+
this.remove$.subscribe((event) => {
|
|
70
64
|
Reflect.deleteProperty(event, EventStoreSymbol);
|
|
71
65
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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.
|
|
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
|
|
236
|
+
/** Removes an event from the store and updates subscriptions */
|
|
234
237
|
remove(event) {
|
|
235
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
316
|
+
return this.memory?.isClaimed(event) ?? false;
|
|
280
317
|
}
|
|
281
318
|
/** Removes a claim from an event */
|
|
282
319
|
removeClaim(event, claim) {
|
|
283
|
-
this.
|
|
320
|
+
return this.memory?.removeClaim(event, claim);
|
|
284
321
|
}
|
|
285
322
|
/** Removes all claims on an event */
|
|
286
323
|
clearClaim(event) {
|
|
287
|
-
this.
|
|
324
|
+
return this.memory?.clearClaim(event);
|
|
288
325
|
}
|
|
289
|
-
/**
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
325
|
-
|
|
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.
|
|
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,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 {
|
|
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[]):
|
|
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
|
|
57
|
-
/**
|
|
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
|
-
|
|
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
|
|
68
|
-
model<T extends unknown, Args extends Array<any>>(constructor: ModelConstructor<T, Args>, ...args: Args): Observable<T>;
|
|
69
|
-
|
|
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
|
-
/**
|
|
77
|
-
export
|
|
78
|
-
/**
|
|
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
|
}
|