applesauce-core 0.0.0-next-20251209200210 → 0.0.0-next-20251220152312
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-factory/methods.js +4 -0
- package/dist/event-store/async-event-store.d.ts +4 -6
- package/dist/event-store/async-event-store.js +55 -56
- package/dist/event-store/event-models.d.ts +5 -22
- package/dist/event-store/event-models.js +10 -7
- package/dist/event-store/event-store.d.ts +4 -4
- package/dist/event-store/event-store.js +72 -46
- package/dist/event-store/interface.d.ts +29 -6
- package/dist/helpers/contacts.d.ts +1 -0
- package/dist/helpers/contacts.js +3 -3
- package/dist/helpers/encrypted-content.d.ts +3 -1
- package/dist/helpers/event-cache.js +1 -1
- package/dist/helpers/event.d.ts +2 -2
- package/dist/helpers/hidden-content.js +4 -4
- package/dist/helpers/hidden-tags.d.ts +6 -2
- package/dist/helpers/hidden-tags.js +9 -3
- package/dist/helpers/pointers.d.ts +2 -2
- package/dist/helpers/pointers.js +12 -3
- package/dist/helpers/profile.d.ts +1 -2
- package/dist/helpers/profile.js +1 -1
- package/dist/helpers/relays.d.ts +2 -0
- package/dist/helpers/relays.js +4 -0
- package/dist/helpers/tags.d.ts +3 -1
- package/dist/helpers/tags.js +5 -1
- package/dist/models/base.js +43 -33
- package/dist/observable/watch-event-updates.d.ts +2 -0
- package/dist/observable/watch-event-updates.js +2 -0
- package/dist/operations/tag/common.d.ts +1 -1
- package/dist/operations/tag/common.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EncryptedContentSymbol } from "../helpers/encrypted-content.js";
|
|
2
|
+
import { isEvent } from "../helpers/event.js";
|
|
2
3
|
import { eventPipe } from "../helpers/pipeline.js";
|
|
3
4
|
import { unixNow } from "../helpers/time.js";
|
|
4
5
|
import { setClient } from "../operations/client.js";
|
|
@@ -36,5 +37,8 @@ export async function createEvent(context, blueprint, ...args) {
|
|
|
36
37
|
}
|
|
37
38
|
/** Modifies an event using a context and a set of operations */
|
|
38
39
|
export async function modifyEvent(event, context, ...operations) {
|
|
40
|
+
// NOTE: Unwrapping evnet object in order to handle cast events from applesauce-common
|
|
41
|
+
if ("event" in event && isEvent(event.event))
|
|
42
|
+
event = event.event;
|
|
39
43
|
return await wrapCommon(stripSignature(), stripStamp(), updateCreatedAt(), ...operations)(event, context);
|
|
40
44
|
}
|
|
@@ -4,7 +4,7 @@ import { Filter } from "../helpers/filter.js";
|
|
|
4
4
|
import { AddressPointer, AddressPointerWithoutD, EventPointer } from "../helpers/pointers.js";
|
|
5
5
|
import { EventMemory } from "./event-memory.js";
|
|
6
6
|
import { EventModels } from "./event-models.js";
|
|
7
|
-
import { IAsyncEventDatabase, IAsyncEventStore,
|
|
7
|
+
import { IAsyncDeleteManager, IAsyncEventDatabase, IAsyncEventStore, IExpirationManager } from "./interface.js";
|
|
8
8
|
export type AsyncEventStoreOptions = {
|
|
9
9
|
/** Keep deleted events in the store */
|
|
10
10
|
keepDeleted?: boolean;
|
|
@@ -44,7 +44,7 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
|
|
|
44
44
|
set verifyEvent(method: undefined | ((event: NostrEvent) => boolean));
|
|
45
45
|
/** A stream of new events added to the store */
|
|
46
46
|
insert$: Subject<import("nostr-tools/core").Event>;
|
|
47
|
-
/** A stream of events that have been updated */
|
|
47
|
+
/** A stream of events that have been updated (Warning: this is a very noisy stream, use with caution) */
|
|
48
48
|
update$: Subject<import("nostr-tools/core").Event>;
|
|
49
49
|
/** A stream of events that have been removed */
|
|
50
50
|
remove$: Subject<import("nostr-tools/core").Event>;
|
|
@@ -59,8 +59,6 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
|
|
|
59
59
|
private handleDeleteNotification;
|
|
60
60
|
/** Handle an expired event by id */
|
|
61
61
|
private handleExpiredNotification;
|
|
62
|
-
/** Copies important metadata from and identical event to another */
|
|
63
|
-
static mergeDuplicateEvent(source: NostrEvent, dest: NostrEvent): void;
|
|
64
62
|
/**
|
|
65
63
|
* Adds an event to the store and update subscriptions
|
|
66
64
|
* @returns The existing event or the event that was added, if it was ignored returns null
|
|
@@ -73,9 +71,9 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
|
|
|
73
71
|
/** Add an event to the store and notifies all subscribes it has updated */
|
|
74
72
|
update(event: NostrEvent): Promise<void>;
|
|
75
73
|
/** Check if the store has an event by id */
|
|
76
|
-
hasEvent(id: string): Promise<boolean>;
|
|
74
|
+
hasEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): Promise<boolean>;
|
|
77
75
|
/** Get an event by id from the store */
|
|
78
|
-
getEvent(id: string): Promise<NostrEvent | undefined>;
|
|
76
|
+
getEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): Promise<NostrEvent | undefined>;
|
|
79
77
|
/** Check if the store has a replaceable event */
|
|
80
78
|
hasReplaceable(kind: number, pubkey: string, d?: string): Promise<boolean>;
|
|
81
79
|
/** Gets the latest version of a replaceable event */
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { verifyEvent as coreVerifyEvent } from "nostr-tools/pure";
|
|
1
2
|
import { Subject } from "rxjs";
|
|
2
|
-
import { EventStoreSymbol,
|
|
3
|
+
import { EventStoreSymbol, getReplaceableIdentifier, isReplaceable, kinds } from "../helpers/event.js";
|
|
3
4
|
import { getExpirationTimestamp } from "../helpers/expiration.js";
|
|
4
|
-
import { eventMatchesPointer,
|
|
5
|
-
import { addSeenRelay
|
|
5
|
+
import { eventMatchesPointer, isAddressPointer, isEventPointer, } from "../helpers/pointers.js";
|
|
6
|
+
import { addSeenRelay } from "../helpers/relays.js";
|
|
6
7
|
import { unixNow } from "../helpers/time.js";
|
|
8
|
+
import { AsyncDeleteManager } from "./async-delete-manager.js";
|
|
7
9
|
import { EventMemory } from "./event-memory.js";
|
|
8
10
|
import { EventModels } from "./event-models.js";
|
|
9
|
-
import {
|
|
10
|
-
import { AsyncDeleteManager } from "./async-delete-manager.js";
|
|
11
|
+
import { EventStore } from "./event-store.js";
|
|
11
12
|
import { ExpirationManager } from "./expiration-manager.js";
|
|
12
13
|
/** An async wrapper around an async event database that handles replaceable events, deletes, and models */
|
|
13
14
|
export class AsyncEventStore extends EventModels {
|
|
@@ -38,7 +39,7 @@ export class AsyncEventStore extends EventModels {
|
|
|
38
39
|
}
|
|
39
40
|
/** A stream of new events added to the store */
|
|
40
41
|
insert$ = new Subject();
|
|
41
|
-
/** A stream of events that have been updated */
|
|
42
|
+
/** A stream of events that have been updated (Warning: this is a very noisy stream, use with caution) */
|
|
42
43
|
update$ = new Subject();
|
|
43
44
|
/** A stream of events that have been removed */
|
|
44
45
|
remove$ = new Subject();
|
|
@@ -75,14 +76,6 @@ export class AsyncEventStore extends EventModels {
|
|
|
75
76
|
console.error("[applesauce-core] Error handling expired notification:", error);
|
|
76
77
|
});
|
|
77
78
|
});
|
|
78
|
-
// when events are added to the database, add the symbol
|
|
79
|
-
this.insert$.subscribe((event) => {
|
|
80
|
-
Reflect.set(event, EventStoreSymbol, this);
|
|
81
|
-
});
|
|
82
|
-
// when events are removed from the database, remove the symbol
|
|
83
|
-
this.remove$.subscribe((event) => {
|
|
84
|
-
Reflect.deleteProperty(event, EventStoreSymbol);
|
|
85
|
-
});
|
|
86
79
|
}
|
|
87
80
|
mapToMemory(event) {
|
|
88
81
|
if (event === undefined)
|
|
@@ -122,18 +115,6 @@ export class AsyncEventStore extends EventModels {
|
|
|
122
115
|
return;
|
|
123
116
|
await this.remove(id);
|
|
124
117
|
}
|
|
125
|
-
/** Copies important metadata from and identical event to another */
|
|
126
|
-
static mergeDuplicateEvent(source, dest) {
|
|
127
|
-
const relays = getSeenRelays(source);
|
|
128
|
-
if (relays) {
|
|
129
|
-
for (const relay of relays)
|
|
130
|
-
addSeenRelay(dest, relay);
|
|
131
|
-
}
|
|
132
|
-
// copy the from cache symbol only if its true
|
|
133
|
-
const fromCache = Reflect.get(source, FromCacheSymbol);
|
|
134
|
-
if (fromCache && !Reflect.get(dest, FromCacheSymbol))
|
|
135
|
-
Reflect.set(dest, FromCacheSymbol, fromCache);
|
|
136
|
-
}
|
|
137
118
|
/**
|
|
138
119
|
* Adds an event to the store and update subscriptions
|
|
139
120
|
* @returns The existing event or the event that was added, if it was ignored returns null
|
|
@@ -151,6 +132,9 @@ export class AsyncEventStore extends EventModels {
|
|
|
151
132
|
const expiration = getExpirationTimestamp(event);
|
|
152
133
|
if (this.keepExpired === false && expiration && expiration <= unixNow())
|
|
153
134
|
return null;
|
|
135
|
+
// Attach relay this event was from
|
|
136
|
+
if (fromRelay)
|
|
137
|
+
addSeenRelay(event, fromRelay);
|
|
154
138
|
// Get the replaceable identifier
|
|
155
139
|
const identifier = isReplaceable(event.kind) ? getReplaceableIdentifier(event) : undefined;
|
|
156
140
|
// Don't insert the event if there is already a newer version
|
|
@@ -158,7 +142,8 @@ export class AsyncEventStore extends EventModels {
|
|
|
158
142
|
const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
|
|
159
143
|
// If there is already a newer version, copy cached symbols and return existing event
|
|
160
144
|
if (existing && existing.length > 0 && existing[0].created_at >= event.created_at) {
|
|
161
|
-
|
|
145
|
+
if (EventStore.copySymbolsToDuplicateEvent(event, existing[0]))
|
|
146
|
+
await this.update(existing[0]);
|
|
162
147
|
return existing[0];
|
|
163
148
|
}
|
|
164
149
|
}
|
|
@@ -166,27 +151,28 @@ export class AsyncEventStore extends EventModels {
|
|
|
166
151
|
if (this.verifyEvent && this.verifyEvent(event) === false)
|
|
167
152
|
return null;
|
|
168
153
|
// Always add event to memory
|
|
169
|
-
const existing = this.memory
|
|
154
|
+
const existing = this.memory.add(event);
|
|
170
155
|
// If the memory returned a different instance, this is a duplicate event
|
|
171
156
|
if (existing && existing !== event) {
|
|
172
157
|
// Copy cached symbols and return existing event
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (fromRelay)
|
|
176
|
-
addSeenRelay(existing, fromRelay);
|
|
158
|
+
if (EventStore.copySymbolsToDuplicateEvent(event, existing))
|
|
159
|
+
await this.update(existing);
|
|
177
160
|
return existing;
|
|
178
161
|
}
|
|
179
162
|
// Insert event into database
|
|
180
163
|
const inserted = this.mapToMemory(await this.database.add(event));
|
|
181
|
-
//
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
addSeenRelay(inserted, fromRelay);
|
|
187
|
-
// Emit insert$ signal
|
|
188
|
-
if (inserted === event)
|
|
164
|
+
// If the event is the same as the inserted event, its a new event
|
|
165
|
+
if (inserted === event) {
|
|
166
|
+
// Set the event store on the event
|
|
167
|
+
Reflect.set(inserted, EventStoreSymbol, this);
|
|
168
|
+
// Emit insert$ signal
|
|
189
169
|
this.insert$.next(inserted);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// Copy cached data if its a duplicate event
|
|
173
|
+
if (EventStore.copySymbolsToDuplicateEvent(event, inserted))
|
|
174
|
+
await this.update(inserted);
|
|
175
|
+
}
|
|
190
176
|
// remove all old version of the replaceable event
|
|
191
177
|
if (this.keepOldVersions === false && isReplaceable(event.kind)) {
|
|
192
178
|
const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
|
|
@@ -208,18 +194,20 @@ export class AsyncEventStore extends EventModels {
|
|
|
208
194
|
/** Removes an event from the store and updates subscriptions */
|
|
209
195
|
async remove(event) {
|
|
210
196
|
const eventId = typeof event === "string" ? event : event.id;
|
|
211
|
-
let instance = this.memory
|
|
197
|
+
let instance = this.memory.getEvent(eventId);
|
|
212
198
|
// Remove from expiration manager
|
|
213
199
|
this.expiration.forget(eventId);
|
|
200
|
+
// Remove the event store from the event
|
|
201
|
+
if (instance)
|
|
202
|
+
Reflect.deleteProperty(instance, EventStoreSymbol);
|
|
214
203
|
// Remove from memory if available
|
|
215
204
|
if (this.memory)
|
|
216
205
|
this.memory.remove(event);
|
|
217
206
|
// Remove the event from the database
|
|
218
207
|
const removed = await this.database.remove(event);
|
|
219
208
|
// If the event was removed, notify the subscriptions
|
|
220
|
-
if (removed && instance)
|
|
209
|
+
if (removed && instance)
|
|
221
210
|
this.remove$.next(instance);
|
|
222
|
-
}
|
|
223
211
|
return removed;
|
|
224
212
|
}
|
|
225
213
|
/** Remove multiple events that match the given filters */
|
|
@@ -252,29 +240,40 @@ export class AsyncEventStore extends EventModels {
|
|
|
252
240
|
}
|
|
253
241
|
/** Check if the store has an event by id */
|
|
254
242
|
async hasEvent(id) {
|
|
255
|
-
|
|
256
|
-
|
|
243
|
+
if (typeof id === "string")
|
|
244
|
+
return this.memory.hasEvent(id) || this.database.hasEvent(id);
|
|
245
|
+
// If its a pointer, use the advanced has event method to resolve
|
|
246
|
+
else if (isEventPointer(id))
|
|
247
|
+
return this.memory.hasEvent(id.id) || this.database.hasEvent(id.id);
|
|
248
|
+
else
|
|
249
|
+
return this.hasReplaceable(id.kind, id.pubkey, id.identifier);
|
|
257
250
|
}
|
|
258
251
|
/** Get an event by id from the store */
|
|
259
252
|
async getEvent(id) {
|
|
260
253
|
// Get the event from memory first, then from the database
|
|
261
|
-
|
|
254
|
+
if (typeof id === "string")
|
|
255
|
+
return this.memory.getEvent(id) ?? this.mapToMemory(await this.database.getEvent(id));
|
|
256
|
+
// If its a pointer, use the advanced get event method to resolve
|
|
257
|
+
else if (isEventPointer(id))
|
|
258
|
+
return this.memory.getEvent(id.id) ?? this.mapToMemory(await this.database.getEvent(id.id));
|
|
259
|
+
else
|
|
260
|
+
return this.getReplaceable(id.kind, id.pubkey, id.identifier);
|
|
262
261
|
}
|
|
263
262
|
/** Check if the store has a replaceable event */
|
|
264
263
|
async hasReplaceable(kind, pubkey, d) {
|
|
265
264
|
// Check if the event exists in memory first, then in the database
|
|
266
|
-
return
|
|
265
|
+
return this.memory.hasReplaceable(kind, pubkey, d) || this.database.hasReplaceable(kind, pubkey, d);
|
|
267
266
|
}
|
|
268
267
|
/** Gets the latest version of a replaceable event */
|
|
269
268
|
async getReplaceable(kind, pubkey, identifier) {
|
|
270
269
|
// Get the event from memory first, then from the database
|
|
271
|
-
return (this.memory
|
|
270
|
+
return (this.memory.getReplaceable(kind, pubkey, identifier) ??
|
|
272
271
|
this.mapToMemory(await this.database.getReplaceable(kind, pubkey, identifier)));
|
|
273
272
|
}
|
|
274
273
|
/** Returns all versions of a replaceable event */
|
|
275
274
|
async getReplaceableHistory(kind, pubkey, identifier) {
|
|
276
275
|
// Get the events from memory first, then from the database
|
|
277
|
-
return (this.memory
|
|
276
|
+
return (this.memory.getReplaceableHistory(kind, pubkey, identifier) ??
|
|
278
277
|
(await this.database.getReplaceableHistory(kind, pubkey, identifier))?.map((e) => this.mapToMemory(e) ?? e));
|
|
279
278
|
}
|
|
280
279
|
/** Get all events matching a filter */
|
|
@@ -297,30 +296,30 @@ export class AsyncEventStore extends EventModels {
|
|
|
297
296
|
}
|
|
298
297
|
/** Passthrough method for the database.touch */
|
|
299
298
|
touch(event) {
|
|
300
|
-
return this.memory
|
|
299
|
+
return this.memory.touch(event);
|
|
301
300
|
}
|
|
302
301
|
/** Increments the claim count on the event and touches it */
|
|
303
302
|
claim(event) {
|
|
304
|
-
return this.memory
|
|
303
|
+
return this.memory.claim(event);
|
|
305
304
|
}
|
|
306
305
|
/** Checks if an event is claimed by anything */
|
|
307
306
|
isClaimed(event) {
|
|
308
|
-
return this.memory
|
|
307
|
+
return this.memory.isClaimed(event) ?? false;
|
|
309
308
|
}
|
|
310
309
|
/** Decrements the claim count on an event */
|
|
311
310
|
removeClaim(event) {
|
|
312
|
-
return this.memory
|
|
311
|
+
return this.memory.removeClaim(event);
|
|
313
312
|
}
|
|
314
313
|
/** Removes all claims on an event */
|
|
315
314
|
clearClaim(event) {
|
|
316
|
-
return this.memory
|
|
315
|
+
return this.memory.clearClaim(event);
|
|
317
316
|
}
|
|
318
317
|
/** Pass through method for the database.unclaimed */
|
|
319
318
|
unclaimed() {
|
|
320
|
-
return this.memory
|
|
319
|
+
return this.memory.unclaimed() || (function* () { })();
|
|
321
320
|
}
|
|
322
321
|
/** Removes any event that is not being used by a subscription */
|
|
323
322
|
prune(limit) {
|
|
324
|
-
return this.memory
|
|
323
|
+
return this.memory.prune(limit) ?? 0;
|
|
325
324
|
}
|
|
326
325
|
}
|
|
@@ -2,24 +2,7 @@ import { Observable } from "rxjs";
|
|
|
2
2
|
import { NostrEvent } from "../helpers/event.js";
|
|
3
3
|
import { Filter } from "../helpers/filter.js";
|
|
4
4
|
import { AddressPointer, AddressPointerWithoutD, EventPointer, ProfilePointer } from "../helpers/pointers.js";
|
|
5
|
-
import {
|
|
6
|
-
import { IAsyncEventStore, IEventStore, ModelConstructor } from "./interface.js";
|
|
7
|
-
/**
|
|
8
|
-
* Core helpful subscriptions interface.
|
|
9
|
-
* Contains only methods that use models from the core package.
|
|
10
|
-
* Other packages (like applesauce-common) can extend this interface via module augmentation.
|
|
11
|
-
*/
|
|
12
|
-
export interface IEventStoreModels {
|
|
13
|
-
/** Subscribe to a users profile */
|
|
14
|
-
profile(user: string | ProfilePointer): Observable<ProfileContent | undefined>;
|
|
15
|
-
/** Subscribe to a users contacts */
|
|
16
|
-
contacts(user: string | ProfilePointer): Observable<ProfilePointer[]>;
|
|
17
|
-
/** Subscribe to a users mailboxes */
|
|
18
|
-
mailboxes(user: string | ProfilePointer): Observable<{
|
|
19
|
-
inboxes: string[];
|
|
20
|
-
outboxes: string[];
|
|
21
|
-
} | undefined>;
|
|
22
|
-
}
|
|
5
|
+
import { IAsyncEventStore, IEventStore, IEventSubscriptions, ModelConstructor } from "./interface.js";
|
|
23
6
|
/**
|
|
24
7
|
* Base class that provides model functionality for both sync and async event stores.
|
|
25
8
|
* This class can be extended by other packages to add additional helpful subscription methods.
|
|
@@ -42,7 +25,7 @@ export interface IEventStoreModels {
|
|
|
42
25
|
* }
|
|
43
26
|
* ```
|
|
44
27
|
*/
|
|
45
|
-
export declare class EventModels<TStore extends IEventStore | IAsyncEventStore = IEventStore | IAsyncEventStore> implements
|
|
28
|
+
export declare class EventModels<TStore extends IEventStore | IAsyncEventStore = IEventStore | IAsyncEventStore> implements IEventSubscriptions {
|
|
46
29
|
/** A directory of all active models */
|
|
47
30
|
models: Map<ModelConstructor<any, any[], TStore>, Map<string, Observable<any>>>;
|
|
48
31
|
/** How long a model should be kept "warm" while nothing is subscribed to it */
|
|
@@ -55,8 +38,8 @@ export declare class EventModels<TStore extends IEventStore | IAsyncEventStore =
|
|
|
55
38
|
* @param [onlyNew=false] Only subscribe to new events
|
|
56
39
|
*/
|
|
57
40
|
filters(filters: Filter | Filter[], onlyNew?: boolean): Observable<NostrEvent>;
|
|
58
|
-
/**
|
|
59
|
-
event(pointer: string | EventPointer): Observable<NostrEvent | undefined>;
|
|
41
|
+
/** Subscribe to an event by pointer */
|
|
42
|
+
event(pointer: string | EventPointer | AddressPointer | AddressPointerWithoutD): Observable<NostrEvent | undefined>;
|
|
60
43
|
/** Subscribe to a replaceable event by pointer */
|
|
61
44
|
replaceable(pointer: AddressPointer | AddressPointerWithoutD): Observable<NostrEvent | undefined>;
|
|
62
45
|
replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
|
|
@@ -65,7 +48,7 @@ export declare class EventModels<TStore extends IEventStore | IAsyncEventStore =
|
|
|
65
48
|
/** Creates a {@link TimelineModel} */
|
|
66
49
|
timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
|
|
67
50
|
/** Subscribe to a users profile */
|
|
68
|
-
profile(user: string | ProfilePointer): Observable<ProfileContent | undefined>;
|
|
51
|
+
profile(user: string | ProfilePointer): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
|
|
69
52
|
/** Subscribe to a users contacts */
|
|
70
53
|
contacts(user: string | ProfilePointer): Observable<ProfilePointer[]>;
|
|
71
54
|
/** Subscribe to a users mailboxes */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import hash_sum from "hash-sum";
|
|
2
|
-
import {
|
|
2
|
+
import { finalize, ReplaySubject, share, timer } from "rxjs";
|
|
3
|
+
import { isEventPointer, } from "../helpers/pointers.js";
|
|
3
4
|
import { EventModel, FiltersModel, ReplaceableModel, TimelineModel } from "../models/base.js";
|
|
4
5
|
import { ContactsModel } from "../models/contacts.js";
|
|
5
6
|
import { MailboxesModel } from "../models/mailboxes.js";
|
|
@@ -67,14 +68,16 @@ export class EventModels {
|
|
|
67
68
|
* @param [onlyNew=false] Only subscribe to new events
|
|
68
69
|
*/
|
|
69
70
|
filters(filters, onlyNew = false) {
|
|
71
|
+
if (!Array.isArray(filters))
|
|
72
|
+
filters = [filters];
|
|
70
73
|
return this.model(FiltersModel, filters, onlyNew);
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
/** Creates a {@link EventModel} */
|
|
75
|
+
/** Subscribe to an event by pointer */
|
|
74
76
|
event(pointer) {
|
|
75
|
-
if (typeof pointer === "string")
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
if (typeof pointer === "string" || isEventPointer(pointer))
|
|
78
|
+
return this.model(EventModel, pointer);
|
|
79
|
+
else
|
|
80
|
+
return this.replaceable(pointer);
|
|
78
81
|
}
|
|
79
82
|
replaceable(...args) {
|
|
80
83
|
let pointer;
|
|
@@ -92,7 +95,7 @@ export class EventModels {
|
|
|
92
95
|
}
|
|
93
96
|
/** Subscribe to an addressable event by pointer */
|
|
94
97
|
addressable(pointer) {
|
|
95
|
-
return this.
|
|
98
|
+
return this.replaceable(pointer);
|
|
96
99
|
}
|
|
97
100
|
/** Creates a {@link TimelineModel} */
|
|
98
101
|
timeline(filters, includeOldVersion = false) {
|
|
@@ -44,7 +44,7 @@ export declare class EventStore extends EventModels implements IEventStore {
|
|
|
44
44
|
set verifyEvent(method: undefined | ((event: NostrEvent) => boolean));
|
|
45
45
|
/** A stream of new events added to the store */
|
|
46
46
|
insert$: Subject<import("nostr-tools/core").Event>;
|
|
47
|
-
/** A stream of events that have been updated */
|
|
47
|
+
/** A stream of events that have been updated (Warning: this is a very noisy stream, use with caution) */
|
|
48
48
|
update$: Subject<import("nostr-tools/core").Event>;
|
|
49
49
|
/** A stream of events that have been removed */
|
|
50
50
|
remove$: Subject<import("nostr-tools/core").Event>;
|
|
@@ -58,7 +58,7 @@ export declare class EventStore extends EventModels implements IEventStore {
|
|
|
58
58
|
/** Handle an expired event by id */
|
|
59
59
|
private handleExpiredNotification;
|
|
60
60
|
/** Copies important metadata from and identical event to another */
|
|
61
|
-
static
|
|
61
|
+
static copySymbolsToDuplicateEvent(source: NostrEvent, dest: NostrEvent): boolean;
|
|
62
62
|
/**
|
|
63
63
|
* Adds an event to the store and update subscriptions
|
|
64
64
|
* @returns The existing event or the event that was added, if it was ignored returns null
|
|
@@ -71,9 +71,9 @@ export declare class EventStore extends EventModels implements IEventStore {
|
|
|
71
71
|
/** Add an event to the store and notifies all subscribes it has updated */
|
|
72
72
|
update(event: NostrEvent): boolean;
|
|
73
73
|
/** Check if the store has an event by id */
|
|
74
|
-
hasEvent(id: string): boolean;
|
|
74
|
+
hasEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): boolean;
|
|
75
75
|
/** Get an event by id from the store */
|
|
76
|
-
getEvent(id: string): NostrEvent | undefined;
|
|
76
|
+
getEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): NostrEvent | undefined;
|
|
77
77
|
/** Check if the store has a replaceable event */
|
|
78
78
|
hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
|
|
79
79
|
/** Gets the latest version of a replaceable event */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { verifyEvent as coreVerifyEvent } from "nostr-tools/pure";
|
|
1
|
+
import { verifyEvent as coreVerifyEvent, verifiedSymbol } from "nostr-tools/pure";
|
|
2
2
|
import { Subject } from "rxjs";
|
|
3
|
-
import {
|
|
3
|
+
import { EncryptedContentSymbol } from "../helpers/encrypted-content.js";
|
|
4
|
+
import { EventStoreSymbol, FromCacheSymbol, getReplaceableIdentifier, isRegularKind, isReplaceable, kinds, } from "../helpers/event.js";
|
|
4
5
|
import { getExpirationTimestamp } from "../helpers/expiration.js";
|
|
5
6
|
import { eventMatchesPointer, isAddressPointer, isEventPointer, } from "../helpers/pointers.js";
|
|
6
7
|
import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
|
|
@@ -38,7 +39,7 @@ export class EventStore extends EventModels {
|
|
|
38
39
|
}
|
|
39
40
|
/** A stream of new events added to the store */
|
|
40
41
|
insert$ = new Subject();
|
|
41
|
-
/** A stream of events that have been updated */
|
|
42
|
+
/** A stream of events that have been updated (Warning: this is a very noisy stream, use with caution) */
|
|
42
43
|
update$ = new Subject();
|
|
43
44
|
/** A stream of events that have been removed */
|
|
44
45
|
remove$ = new Subject();
|
|
@@ -71,14 +72,6 @@ export class EventStore extends EventModels {
|
|
|
71
72
|
this.expiration = options?.expirationManager ?? new ExpirationManager();
|
|
72
73
|
// Listen to expired events and remove them from the store
|
|
73
74
|
this.expiration.expired$.subscribe(this.handleExpiredNotification.bind(this));
|
|
74
|
-
// when events are added to the database, add the symbol
|
|
75
|
-
this.insert$.subscribe((event) => {
|
|
76
|
-
Reflect.set(event, EventStoreSymbol, this);
|
|
77
|
-
});
|
|
78
|
-
// when events are removed from the database, remove the symbol
|
|
79
|
-
this.remove$.subscribe((event) => {
|
|
80
|
-
Reflect.deleteProperty(event, EventStoreSymbol);
|
|
81
|
-
});
|
|
82
75
|
}
|
|
83
76
|
mapToMemory(event) {
|
|
84
77
|
if (event === undefined)
|
|
@@ -120,16 +113,31 @@ export class EventStore extends EventModels {
|
|
|
120
113
|
this.remove(id);
|
|
121
114
|
}
|
|
122
115
|
/** Copies important metadata from and identical event to another */
|
|
123
|
-
static
|
|
116
|
+
static copySymbolsToDuplicateEvent(source, dest) {
|
|
117
|
+
if (source.kind !== dest.kind)
|
|
118
|
+
throw new Error("Source and destination events must have the same kind");
|
|
119
|
+
if (isRegularKind(source.kind) && source.id !== dest.id)
|
|
120
|
+
throw new Error("Source and destination events must have the same ID");
|
|
121
|
+
if (isReplaceable(source.kind) &&
|
|
122
|
+
source.pubkey !== dest.pubkey &&
|
|
123
|
+
getReplaceableIdentifier(source) !== getReplaceableIdentifier(dest))
|
|
124
|
+
throw new Error("Source and destination events must have the same pubkey and replaceable identifier");
|
|
125
|
+
let changed = false;
|
|
126
|
+
// Merge seen relays
|
|
124
127
|
const relays = getSeenRelays(source);
|
|
125
128
|
if (relays) {
|
|
126
129
|
for (const relay of relays)
|
|
127
130
|
addSeenRelay(dest, relay);
|
|
131
|
+
changed = true;
|
|
128
132
|
}
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
const symbols = [FromCacheSymbol, verifiedSymbol, EncryptedContentSymbol];
|
|
134
|
+
for (const symbol of symbols) {
|
|
135
|
+
if (symbol in source && !(symbol in dest)) {
|
|
136
|
+
Reflect.set(dest, symbol, Reflect.get(source, symbol));
|
|
137
|
+
changed = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return changed;
|
|
133
141
|
}
|
|
134
142
|
/**
|
|
135
143
|
* Adds an event to the store and update subscriptions
|
|
@@ -148,6 +156,9 @@ export class EventStore extends EventModels {
|
|
|
148
156
|
const expiration = getExpirationTimestamp(event);
|
|
149
157
|
if (this.keepExpired === false && expiration && expiration <= unixNow())
|
|
150
158
|
return null;
|
|
159
|
+
// Attach relay this event was from
|
|
160
|
+
if (fromRelay)
|
|
161
|
+
addSeenRelay(event, fromRelay);
|
|
151
162
|
// Get the replaceable identifier
|
|
152
163
|
const identifier = isReplaceable(event.kind) ? getReplaceableIdentifier(event) : undefined;
|
|
153
164
|
// Don't insert the event if there is already a newer version
|
|
@@ -155,7 +166,8 @@ export class EventStore extends EventModels {
|
|
|
155
166
|
const existing = this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
|
|
156
167
|
// If there is already a newer version, copy cached symbols and return existing event
|
|
157
168
|
if (existing && existing.length > 0 && existing[0].created_at >= event.created_at) {
|
|
158
|
-
EventStore.
|
|
169
|
+
if (EventStore.copySymbolsToDuplicateEvent(event, existing[0]))
|
|
170
|
+
this.update(existing[0]);
|
|
159
171
|
return existing[0];
|
|
160
172
|
}
|
|
161
173
|
}
|
|
@@ -163,27 +175,28 @@ export class EventStore extends EventModels {
|
|
|
163
175
|
if (this.verifyEvent && this.verifyEvent(event) === false)
|
|
164
176
|
return null;
|
|
165
177
|
// Always add event to memory
|
|
166
|
-
const existing = this.memory
|
|
178
|
+
const existing = this.memory.add(event);
|
|
167
179
|
// If the memory returned a different instance, this is a duplicate event
|
|
168
180
|
if (existing && existing !== event) {
|
|
169
181
|
// Copy cached symbols and return existing event
|
|
170
|
-
EventStore.
|
|
171
|
-
|
|
172
|
-
if (fromRelay)
|
|
173
|
-
addSeenRelay(existing, fromRelay);
|
|
182
|
+
if (EventStore.copySymbolsToDuplicateEvent(event, existing))
|
|
183
|
+
this.update(existing);
|
|
174
184
|
return existing;
|
|
175
185
|
}
|
|
176
186
|
// Insert event into database
|
|
177
187
|
const inserted = this.mapToMemory(this.database.add(event));
|
|
178
|
-
//
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
addSeenRelay(inserted, fromRelay);
|
|
184
|
-
// Emit insert$ signal
|
|
185
|
-
if (inserted === event)
|
|
188
|
+
// If the event is the same as the inserted event, its a new event
|
|
189
|
+
if (inserted === event) {
|
|
190
|
+
// Set the event store on the event
|
|
191
|
+
Reflect.set(inserted, EventStoreSymbol, this);
|
|
192
|
+
// Emit insert$ signal
|
|
186
193
|
this.insert$.next(inserted);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Copy cached data if its a duplicate event
|
|
197
|
+
if (EventStore.copySymbolsToDuplicateEvent(event, inserted))
|
|
198
|
+
this.update(inserted);
|
|
199
|
+
}
|
|
187
200
|
// remove all old version of the replaceable event
|
|
188
201
|
if (this.keepOldVersions === false && isReplaceable(event.kind)) {
|
|
189
202
|
const existing = this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
|
|
@@ -205,18 +218,20 @@ export class EventStore extends EventModels {
|
|
|
205
218
|
/** Removes an event from the store and updates subscriptions */
|
|
206
219
|
remove(event) {
|
|
207
220
|
const eventId = typeof event === "string" ? event : event.id;
|
|
208
|
-
let instance = this.memory
|
|
221
|
+
let instance = this.memory.getEvent(eventId);
|
|
209
222
|
// Remove from expiration manager
|
|
210
223
|
this.expiration.forget(eventId);
|
|
224
|
+
// Remove the event store from the event
|
|
225
|
+
if (instance)
|
|
226
|
+
Reflect.deleteProperty(instance, EventStoreSymbol);
|
|
211
227
|
// Remove from memory if it's not the same as the database
|
|
212
228
|
if (this.memory !== this.database)
|
|
213
229
|
this.memory.remove(event);
|
|
214
230
|
// Remove the event from the database
|
|
215
231
|
const removed = this.database.remove(event);
|
|
216
232
|
// If the event was removed, notify the subscriptions
|
|
217
|
-
if (removed && instance)
|
|
233
|
+
if (removed && instance)
|
|
218
234
|
this.remove$.next(instance);
|
|
219
|
-
}
|
|
220
235
|
return removed;
|
|
221
236
|
}
|
|
222
237
|
/** Remove multiple events that match the given filters */
|
|
@@ -250,29 +265,40 @@ export class EventStore extends EventModels {
|
|
|
250
265
|
}
|
|
251
266
|
/** Check if the store has an event by id */
|
|
252
267
|
hasEvent(id) {
|
|
253
|
-
|
|
254
|
-
|
|
268
|
+
if (typeof id === "string")
|
|
269
|
+
return this.memory.hasEvent(id) || this.database.hasEvent(id);
|
|
270
|
+
// If its a pointer, use the advanced has event method to resolve
|
|
271
|
+
else if (isEventPointer(id))
|
|
272
|
+
return this.memory.hasEvent(id.id) || this.database.hasEvent(id.id);
|
|
273
|
+
else
|
|
274
|
+
return this.hasReplaceable(id.kind, id.pubkey, id.identifier);
|
|
255
275
|
}
|
|
256
276
|
/** Get an event by id from the store */
|
|
257
277
|
getEvent(id) {
|
|
258
278
|
// Get the event from memory first, then from the database
|
|
259
|
-
|
|
279
|
+
if (typeof id === "string")
|
|
280
|
+
return this.memory.getEvent(id) ?? this.mapToMemory(this.database.getEvent(id));
|
|
281
|
+
// If its a pointer, use the advanced get event method to resolve
|
|
282
|
+
else if (isEventPointer(id))
|
|
283
|
+
return this.memory.getEvent(id.id) ?? this.mapToMemory(this.database.getEvent(id.id));
|
|
284
|
+
else
|
|
285
|
+
return this.getReplaceable(id.kind, id.pubkey, id.identifier);
|
|
260
286
|
}
|
|
261
287
|
/** Check if the store has a replaceable event */
|
|
262
288
|
hasReplaceable(kind, pubkey, d) {
|
|
263
289
|
// Check if the event exists in memory first, then in the database
|
|
264
|
-
return this.memory
|
|
290
|
+
return this.memory.hasReplaceable(kind, pubkey, d) || this.database.hasReplaceable(kind, pubkey, d);
|
|
265
291
|
}
|
|
266
292
|
/** Gets the latest version of a replaceable event */
|
|
267
293
|
getReplaceable(kind, pubkey, identifier) {
|
|
268
294
|
// Get the event from memory first, then from the database
|
|
269
|
-
return (this.memory
|
|
295
|
+
return (this.memory.getReplaceable(kind, pubkey, identifier) ??
|
|
270
296
|
this.mapToMemory(this.database.getReplaceable(kind, pubkey, identifier)));
|
|
271
297
|
}
|
|
272
298
|
/** Returns all versions of a replaceable event */
|
|
273
299
|
getReplaceableHistory(kind, pubkey, identifier) {
|
|
274
300
|
// Get the events from memory first, then from the database
|
|
275
|
-
return (this.memory
|
|
301
|
+
return (this.memory.getReplaceableHistory(kind, pubkey, identifier) ??
|
|
276
302
|
this.database.getReplaceableHistory(kind, pubkey, identifier)?.map((e) => this.mapToMemory(e) ?? e));
|
|
277
303
|
}
|
|
278
304
|
/** Get all events matching a filter */
|
|
@@ -295,30 +321,30 @@ export class EventStore extends EventModels {
|
|
|
295
321
|
}
|
|
296
322
|
/** Passthrough method for the database.touch */
|
|
297
323
|
touch(event) {
|
|
298
|
-
return this.memory
|
|
324
|
+
return this.memory.touch(event);
|
|
299
325
|
}
|
|
300
326
|
/** Increments the claim count on the event and touches it */
|
|
301
327
|
claim(event) {
|
|
302
|
-
return this.memory
|
|
328
|
+
return this.memory.claim(event);
|
|
303
329
|
}
|
|
304
330
|
/** Checks if an event is claimed by anything */
|
|
305
331
|
isClaimed(event) {
|
|
306
|
-
return this.memory
|
|
332
|
+
return this.memory.isClaimed(event) ?? false;
|
|
307
333
|
}
|
|
308
334
|
/** Decrements the claim count on an event */
|
|
309
335
|
removeClaim(event) {
|
|
310
|
-
return this.memory
|
|
336
|
+
return this.memory.removeClaim(event);
|
|
311
337
|
}
|
|
312
338
|
/** Removes all claims on an event */
|
|
313
339
|
clearClaim(event) {
|
|
314
|
-
return this.memory
|
|
340
|
+
return this.memory.clearClaim(event);
|
|
315
341
|
}
|
|
316
342
|
/** Pass through method for the database.unclaimed */
|
|
317
343
|
unclaimed() {
|
|
318
|
-
return this.memory
|
|
344
|
+
return this.memory.unclaimed() || (function* () { })();
|
|
319
345
|
}
|
|
320
346
|
/** Removes any event that is not being used by a subscription */
|
|
321
347
|
prune(limit) {
|
|
322
|
-
return this.memory
|
|
348
|
+
return this.memory.prune(limit) ?? 0;
|
|
323
349
|
}
|
|
324
350
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Observable } from "rxjs";
|
|
2
2
|
import { NostrEvent } from "../helpers/event.js";
|
|
3
3
|
import { Filter } from "../helpers/filter.js";
|
|
4
|
-
import { AddressPointer, AddressPointerWithoutD, EventPointer } from "../helpers/pointers.js";
|
|
5
|
-
import {
|
|
4
|
+
import { AddressPointer, AddressPointerWithoutD, EventPointer, ProfilePointer } from "../helpers/pointers.js";
|
|
5
|
+
import { ProfileContent } from "../helpers/profile.js";
|
|
6
6
|
/** The read interface for an event store */
|
|
7
7
|
export interface IEventStoreRead {
|
|
8
8
|
/** Check if the event store has an event with id */
|
|
@@ -37,6 +37,20 @@ export interface IAsyncEventStoreRead {
|
|
|
37
37
|
/** Get a timeline of events that match the filters */
|
|
38
38
|
getTimeline(filters: Filter | Filter[]): Promise<NostrEvent[]>;
|
|
39
39
|
}
|
|
40
|
+
/** An extended read interface for an event store that supports pointers */
|
|
41
|
+
export interface IEventStoreReadAdvanced extends Omit<IEventStoreRead, "hasEvent" | "getEvent"> {
|
|
42
|
+
/** Check if the event store has an event with id */
|
|
43
|
+
hasEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): boolean;
|
|
44
|
+
/** Get an event by id */
|
|
45
|
+
getEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): NostrEvent | undefined;
|
|
46
|
+
}
|
|
47
|
+
/** An extended async read interface for an event store that supports pointers */
|
|
48
|
+
export interface IAsyncEventStoreReadAdvanced extends Omit<IAsyncEventStoreRead, "hasEvent" | "getEvent"> {
|
|
49
|
+
/** Check if the event store has an event with id */
|
|
50
|
+
hasEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): Promise<boolean>;
|
|
51
|
+
/** Get an event by id */
|
|
52
|
+
getEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): Promise<NostrEvent | undefined>;
|
|
53
|
+
}
|
|
40
54
|
/** The stream interface for an event store */
|
|
41
55
|
export interface IEventStoreStreams {
|
|
42
56
|
/** A stream of new events added to the store */
|
|
@@ -81,8 +95,8 @@ export interface IEventClaims {
|
|
|
81
95
|
}
|
|
82
96
|
/** An event store that can be subscribed to */
|
|
83
97
|
export interface IEventSubscriptions {
|
|
84
|
-
/** Subscribe to an event by id */
|
|
85
|
-
event(id: string | EventPointer): Observable<NostrEvent | undefined>;
|
|
98
|
+
/** Subscribe to an event by id or pointer */
|
|
99
|
+
event(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): Observable<NostrEvent | undefined>;
|
|
86
100
|
/** Subscribe to a replaceable event by pointer */
|
|
87
101
|
replaceable(pointer: AddressPointerWithoutD): Observable<NostrEvent | undefined>;
|
|
88
102
|
/** Subscribe to a replaceable event with legacy arguments */
|
|
@@ -93,6 +107,15 @@ export interface IEventSubscriptions {
|
|
|
93
107
|
filters(filters: Filter | Filter[], onlyNew?: boolean): Observable<NostrEvent>;
|
|
94
108
|
/** Subscribe to a sorted timeline of events that match the filters */
|
|
95
109
|
timeline(filters: Filter | Filter[], onlyNew?: boolean): Observable<NostrEvent[]>;
|
|
110
|
+
/** Subscribe to a users profile */
|
|
111
|
+
profile(user: string | ProfilePointer): Observable<ProfileContent | undefined>;
|
|
112
|
+
/** Subscribe to a users contacts */
|
|
113
|
+
contacts(user: string | ProfilePointer): Observable<ProfilePointer[]>;
|
|
114
|
+
/** Subscribe to a users mailboxes */
|
|
115
|
+
mailboxes(user: string | ProfilePointer): Observable<{
|
|
116
|
+
inboxes: string[];
|
|
117
|
+
outboxes: string[];
|
|
118
|
+
} | undefined>;
|
|
96
119
|
}
|
|
97
120
|
/** Methods for creating common models */
|
|
98
121
|
export interface IEventModelMixin<TStore extends IEventStore | IAsyncEventStore> {
|
|
@@ -185,8 +208,8 @@ export interface IMissingEventLoader {
|
|
|
185
208
|
eventLoader?: (pointer: EventPointer | AddressPointer | AddressPointerWithoutD) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
|
|
186
209
|
}
|
|
187
210
|
/** Generic async event store interface */
|
|
188
|
-
export interface IAsyncEventStore extends
|
|
211
|
+
export interface IAsyncEventStore extends IAsyncEventStoreReadAdvanced, IEventStoreStreams, IEventSubscriptions, IAsyncEventStoreActions, IEventModelMixin<IAsyncEventStore>, IEventClaims, IMissingEventLoader {
|
|
189
212
|
}
|
|
190
213
|
/** Generic sync event store interface */
|
|
191
|
-
export interface IEventStore extends
|
|
214
|
+
export interface IEventStore extends IEventStoreReadAdvanced, IEventStoreStreams, IEventSubscriptions, IEventStoreActions, IEventModelMixin<IEventStore>, IEventClaims, IMissingEventLoader {
|
|
192
215
|
}
|
|
@@ -18,6 +18,7 @@ export declare function getPublicContacts(event: NostrEvent): ProfilePointer[];
|
|
|
18
18
|
/** Checks if the hidden contacts are unlocked */
|
|
19
19
|
export declare function isHiddenContactsUnlocked<T extends NostrEvent>(event: T): event is T & UnlockedContacts;
|
|
20
20
|
/** Returns only the hidden contacts from a contacts list event */
|
|
21
|
+
export declare function getHiddenContacts(event: UnlockedContacts): ProfilePointer[];
|
|
21
22
|
export declare function getHiddenContacts(event: NostrEvent): ProfilePointer[] | undefined;
|
|
22
23
|
/** Unlocks the hidden contacts */
|
|
23
24
|
export declare function unlockHiddenContacts(event: NostrEvent, signer: HiddenContentSigner): Promise<ProfilePointer[]>;
|
package/dist/helpers/contacts.js
CHANGED
|
@@ -54,11 +54,11 @@ export function getPublicContacts(event) {
|
|
|
54
54
|
}
|
|
55
55
|
/** Checks if the hidden contacts are unlocked */
|
|
56
56
|
export function isHiddenContactsUnlocked(event) {
|
|
57
|
-
|
|
57
|
+
// No need for try catch or proactivly parsing here since it only depends on hidden tags
|
|
58
|
+
return isHiddenTagsUnlocked(event);
|
|
58
59
|
}
|
|
59
|
-
/** Returns only the hidden contacts from a contacts list event */
|
|
60
60
|
export function getHiddenContacts(event) {
|
|
61
|
-
if (
|
|
61
|
+
if (HiddenContactsSymbol in event)
|
|
62
62
|
return event[HiddenContactsSymbol];
|
|
63
63
|
// Get hidden tags
|
|
64
64
|
const tags = getHiddenTags(event);
|
|
@@ -45,7 +45,9 @@ export declare function hasEncryptedContent<T extends {
|
|
|
45
45
|
export declare function getEncryptedContent<T extends UnlockedEncryptedContent>(event: T): string;
|
|
46
46
|
export declare function getEncryptedContent<T extends object>(event: T): string | undefined;
|
|
47
47
|
/** Checks if the encrypted content is unlocked and casts it to the {@link UnlockedEncryptedContent} type */
|
|
48
|
-
export declare function isEncryptedContentUnlocked<T extends
|
|
48
|
+
export declare function isEncryptedContentUnlocked<T extends {
|
|
49
|
+
kind: number;
|
|
50
|
+
}>(event: T): event is T & UnlockedEncryptedContent;
|
|
49
51
|
/**
|
|
50
52
|
* Unlocks the encrypted content in an event and caches it
|
|
51
53
|
* @param event The event with content to decrypt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { bufferTime, filter } from "rxjs";
|
|
2
2
|
import { logger } from "../logger.js";
|
|
3
|
-
import { isFromCache } from "./
|
|
3
|
+
import { isFromCache } from "./event.js";
|
|
4
4
|
const log = logger.extend("event-cache");
|
|
5
5
|
/**
|
|
6
6
|
* Setups a process to write batches of new events from an event store to a cache
|
package/dist/helpers/event.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
2
|
import { isAddressableKind, isEphemeralKind, isRegularKind, isReplaceableKind } from "nostr-tools/kinds";
|
|
3
3
|
import { NostrEvent, VerifiedEvent } from "nostr-tools/pure";
|
|
4
|
-
import { IEventStore } from "../event-store/interface.js";
|
|
4
|
+
import { IAsyncEventStore, IEventStore } from "../event-store/interface.js";
|
|
5
5
|
export { EventTemplate, finalizeEvent, getEventHash, NostrEvent, serializeEvent, UnsignedEvent, VerifiedEvent, verifiedSymbol, verifyEvent, } from "nostr-tools/pure";
|
|
6
6
|
export { binarySearch, bytesToHex, hexToBytes, insertEventIntoAscendingList, insertEventIntoDescendingList, } from "nostr-tools/utils";
|
|
7
7
|
export { isAddressableKind, isEphemeralKind, isRegularKind, isReplaceableKind, kinds };
|
|
@@ -51,7 +51,7 @@ export declare function markFromCache(event: NostrEvent): void;
|
|
|
51
51
|
/** Returns if an event was from a cache */
|
|
52
52
|
export declare function isFromCache(event: NostrEvent): boolean;
|
|
53
53
|
/** Returns the EventStore of an event if its been added to one */
|
|
54
|
-
export declare function getParentEventStore<T extends object>(event: T): IEventStore | undefined;
|
|
54
|
+
export declare function getParentEventStore<T extends object>(event: T): IEventStore | IAsyncEventStore | undefined;
|
|
55
55
|
/** Notifies the events parent store that an event has been updated */
|
|
56
56
|
export declare function notifyEventUpdate(event: any): void;
|
|
57
57
|
/** Returns the replaceable identifier for a replaceable event */
|
|
@@ -28,8 +28,8 @@ export function isHiddenContentUnlocked(event) {
|
|
|
28
28
|
export function getHiddenContent(event) {
|
|
29
29
|
if (!canHaveHiddenContent(event.kind))
|
|
30
30
|
return undefined;
|
|
31
|
-
if (
|
|
32
|
-
return event
|
|
31
|
+
if (HiddenContentSymbol in event)
|
|
32
|
+
return Reflect.get(event, HiddenContentSymbol);
|
|
33
33
|
return getEncryptedContent(event);
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
@@ -42,8 +42,8 @@ export async function unlockHiddenContent(event, signer, override) {
|
|
|
42
42
|
if (!canHaveHiddenContent(event.kind))
|
|
43
43
|
throw new Error("Event kind does not support hidden content");
|
|
44
44
|
// If the encrypted content is already unlocked, return the cached value
|
|
45
|
-
if (
|
|
46
|
-
return event
|
|
45
|
+
if (HiddenContentSymbol in event)
|
|
46
|
+
return Reflect.get(event, HiddenContentSymbol);
|
|
47
47
|
// Get the encryption method from the signer
|
|
48
48
|
const encryption = getEncryptedContentEncryptionMethods(event.kind, signer, override);
|
|
49
49
|
// Decrypt the content using the events pubkey
|
|
@@ -23,7 +23,10 @@ export declare function getHiddenTagsEncryptionMethods(kind: number, signer: Hid
|
|
|
23
23
|
export declare function isHiddenTagsUnlocked<T extends {
|
|
24
24
|
kind: number;
|
|
25
25
|
}>(event: T): event is T & UnlockedHiddenTags;
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Returns the hidden tags for an event if they are unlocked
|
|
28
|
+
* @throws {Error} If the hidden content is not an array of tags
|
|
29
|
+
*/
|
|
27
30
|
export declare function getHiddenTags<T extends {
|
|
28
31
|
kind: number;
|
|
29
32
|
} & UnlockedHiddenTags>(event: T): string[][];
|
|
@@ -35,7 +38,8 @@ export declare function getHiddenTags<T extends {
|
|
|
35
38
|
* @param event The list event to decrypt
|
|
36
39
|
* @param signer A signer to use to decrypt the tags
|
|
37
40
|
* @param override The encryption method to use instead of the default
|
|
38
|
-
* @throws
|
|
41
|
+
* @throws {Error} If the event kind does not support hidden tags
|
|
42
|
+
* @throws {Error} If the hidden content is not an array of tags
|
|
39
43
|
*/
|
|
40
44
|
export declare function unlockHiddenTags<T extends {
|
|
41
45
|
kind: number;
|
|
@@ -42,13 +42,18 @@ export function getHiddenTagsEncryptionMethods(kind, signer) {
|
|
|
42
42
|
export function isHiddenTagsUnlocked(event) {
|
|
43
43
|
if (!canHaveHiddenTags(event.kind))
|
|
44
44
|
return false;
|
|
45
|
-
|
|
45
|
+
// Wrap in try catch to avoid throwing validation errors
|
|
46
|
+
try {
|
|
47
|
+
return HiddenTagsSymbol in event || (isHiddenContentUnlocked(event) && getHiddenTags(event) !== undefined);
|
|
48
|
+
}
|
|
49
|
+
catch { }
|
|
50
|
+
return false;
|
|
46
51
|
}
|
|
47
52
|
export function getHiddenTags(event) {
|
|
48
53
|
if (!canHaveHiddenTags(event.kind))
|
|
49
54
|
return undefined;
|
|
50
55
|
// If the hidden tags are already unlocked, return the cached value
|
|
51
|
-
if (
|
|
56
|
+
if (HiddenTagsSymbol in event)
|
|
52
57
|
return event[HiddenTagsSymbol];
|
|
53
58
|
// unlock hidden content is needed
|
|
54
59
|
const content = getHiddenContent(event);
|
|
@@ -71,7 +76,8 @@ export function getHiddenTags(event) {
|
|
|
71
76
|
* @param event The list event to decrypt
|
|
72
77
|
* @param signer A signer to use to decrypt the tags
|
|
73
78
|
* @param override The encryption method to use instead of the default
|
|
74
|
-
* @throws
|
|
79
|
+
* @throws {Error} If the event kind does not support hidden tags
|
|
80
|
+
* @throws {Error} If the hidden content is not an array of tags
|
|
75
81
|
*/
|
|
76
82
|
export async function unlockHiddenTags(event, signer, override) {
|
|
77
83
|
if (!canHaveHiddenTags(event.kind))
|
|
@@ -29,9 +29,9 @@ export declare function getAddressPointerFromATag(tag: string[]): AddressPointer
|
|
|
29
29
|
/** Gets a ProfilePointer from a common "p" tag */
|
|
30
30
|
export declare function getProfilePointerFromPTag(tag: string[]): ProfilePointer | null;
|
|
31
31
|
/** Checks if a pointer is an AddressPointer */
|
|
32
|
-
export declare function isAddressPointer(pointer:
|
|
32
|
+
export declare function isAddressPointer(pointer: any): pointer is AddressPointer;
|
|
33
33
|
/** Checks if a pointer is an EventPointer */
|
|
34
|
-
export declare function isEventPointer(pointer:
|
|
34
|
+
export declare function isEventPointer(pointer: any): pointer is EventPointer;
|
|
35
35
|
/** Returns the stringified address pointer */
|
|
36
36
|
export declare function getReplaceableAddressFromPointer(pointer: AddressPointer): string;
|
|
37
37
|
/** Returns an AddressPointer for a replaceable event */
|
package/dist/helpers/pointers.js
CHANGED
|
@@ -40,13 +40,15 @@ export function parseReplaceableAddress(address, requireIdentifier = false) {
|
|
|
40
40
|
const parts = address.split(":");
|
|
41
41
|
const kind = parts[0] ? parseInt(parts[0]) : undefined;
|
|
42
42
|
const pubkey = parts[1];
|
|
43
|
-
const identifier = parts[2] ?? "";
|
|
44
43
|
// Check valid kind
|
|
45
44
|
if (kind === undefined)
|
|
46
45
|
return null;
|
|
47
46
|
// Check valid pubkey
|
|
48
47
|
if (pubkey === undefined || pubkey === "" || !isHexKey(pubkey))
|
|
49
48
|
return null;
|
|
49
|
+
// Reconstruct identifier by joining all remaining parts after pubkey
|
|
50
|
+
// This handles cases where the identifier contains colons (e.g., URLs)
|
|
51
|
+
const identifier = parts.slice(2).join(":");
|
|
50
52
|
// Return null if identifier is required and missing
|
|
51
53
|
if (requireIdentifier && identifier === "")
|
|
52
54
|
return null;
|
|
@@ -149,11 +151,18 @@ export function getProfilePointerFromPTag(tag) {
|
|
|
149
151
|
}
|
|
150
152
|
/** Checks if a pointer is an AddressPointer */
|
|
151
153
|
export function isAddressPointer(pointer) {
|
|
152
|
-
return typeof pointer
|
|
154
|
+
return (typeof pointer === "object" &&
|
|
155
|
+
pointer !== null &&
|
|
156
|
+
"identifier" in pointer &&
|
|
157
|
+
"pubkey" in pointer &&
|
|
158
|
+
"kind" in pointer &&
|
|
159
|
+
typeof pointer.identifier === "string" &&
|
|
160
|
+
typeof pointer.pubkey === "string" &&
|
|
161
|
+
typeof pointer.kind === "number");
|
|
153
162
|
}
|
|
154
163
|
/** Checks if a pointer is an EventPointer */
|
|
155
164
|
export function isEventPointer(pointer) {
|
|
156
|
-
return typeof pointer
|
|
165
|
+
return typeof pointer === "object" && pointer !== null && "id" in pointer && typeof pointer.id === "string";
|
|
157
166
|
}
|
|
158
167
|
/** Returns the stringified address pointer */
|
|
159
168
|
export function getReplaceableAddressFromPointer(pointer) {
|
package/dist/helpers/profile.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getOrComputeCachedValue } from "./cache.js";
|
|
2
2
|
import { kinds } from "./event.js";
|
|
3
|
-
import { safeParse } from "./
|
|
3
|
+
import { safeParse } from "./json.js";
|
|
4
4
|
import { npubEncode } from "./pointers.js";
|
|
5
5
|
export const ProfileContentSymbol = Symbol.for("profile-content");
|
|
6
6
|
export function getProfileContent(event) {
|
package/dist/helpers/relays.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ declare module "nostr-tools" {
|
|
|
9
9
|
export declare function addSeenRelay(event: NostrEvent, relay: string): Set<string>;
|
|
10
10
|
/** Returns the set of relays this event was seen on */
|
|
11
11
|
export declare function getSeenRelays(event: NostrEvent): Set<string> | undefined;
|
|
12
|
+
/** Checks if an event was received from a specific relay */
|
|
13
|
+
export declare function isFromRelay(event: NostrEvent, relay: string): boolean;
|
|
12
14
|
/** A fast check to make sure relay hints are safe to connect to */
|
|
13
15
|
export declare function isSafeRelayURL(relay: string): boolean;
|
|
14
16
|
/** Merge multiple sets of relays and remove duplicates (ignores invalid URLs) */
|
package/dist/helpers/relays.js
CHANGED
|
@@ -17,6 +17,10 @@ export function addSeenRelay(event, relay) {
|
|
|
17
17
|
export function getSeenRelays(event) {
|
|
18
18
|
return Reflect.get(event, SeenRelaysSymbol);
|
|
19
19
|
}
|
|
20
|
+
/** Checks if an event was received from a specific relay */
|
|
21
|
+
export function isFromRelay(event, relay) {
|
|
22
|
+
return getSeenRelays(event)?.has(relay) === true;
|
|
23
|
+
}
|
|
20
24
|
const WEBSOCKET_URL_CHECK = /^wss?:\/\/([-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}|localhost)\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)$/;
|
|
21
25
|
/** A fast check to make sure relay hints are safe to connect to */
|
|
22
26
|
export function isSafeRelayURL(relay) {
|
package/dist/helpers/tags.d.ts
CHANGED
|
@@ -12,8 +12,10 @@ export declare function isRTag(tag: string[]): tag is ["r", string, ...string[]]
|
|
|
12
12
|
export declare function isDTag(tag: string[]): tag is ["d", string, ...string[]];
|
|
13
13
|
/** Checks if tag is an "a" tag and has at least one value */
|
|
14
14
|
export declare function isATag(tag: string[]): tag is ["a", string, ...string[]];
|
|
15
|
-
/** Checks if tag is an "
|
|
15
|
+
/** Checks if tag is an "t" tag and has at least one value */
|
|
16
16
|
export declare function isTTag(tag: string[]): tag is ["t", string, ...string[]];
|
|
17
|
+
/** Checks if tag is an "q" tag and has at least one value */
|
|
18
|
+
export declare function isQTag(tag: string[]): tag is ["q", string, ...string[]];
|
|
17
19
|
/** Filter and transform tags */
|
|
18
20
|
export declare const processTags: TagPipe;
|
|
19
21
|
/** A pipeline that filters and maps each tag */
|
package/dist/helpers/tags.js
CHANGED
|
@@ -22,10 +22,14 @@ export function isDTag(tag) {
|
|
|
22
22
|
export function isATag(tag) {
|
|
23
23
|
return isNameValueTag(tag, "a");
|
|
24
24
|
}
|
|
25
|
-
/** Checks if tag is an "
|
|
25
|
+
/** Checks if tag is an "t" tag and has at least one value */
|
|
26
26
|
export function isTTag(tag) {
|
|
27
27
|
return isNameValueTag(tag, "t");
|
|
28
28
|
}
|
|
29
|
+
/** Checks if tag is an "q" tag and has at least one value */
|
|
30
|
+
export function isQTag(tag) {
|
|
31
|
+
return isNameValueTag(tag, "q");
|
|
32
|
+
}
|
|
29
33
|
/** Filter and transform tags */
|
|
30
34
|
export const processTags = (tags, ...fns) => {
|
|
31
35
|
return fns.reduce((step, fn) => {
|
package/dist/models/base.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { defer, distinctUntilChanged, EMPTY,
|
|
1
|
+
import { defer, distinctUntilChanged, EMPTY, filter, finalize, from, identity, map, merge, mergeMap, mergeWith, of, scan, startWith, switchMap, take, tap, } from "rxjs";
|
|
2
2
|
import { getEventUID, getReplaceableIdentifier, insertEventIntoDescendingList, isReplaceable, } from "../helpers/event.js";
|
|
3
3
|
import { matchFilters } from "../helpers/filter.js";
|
|
4
4
|
import { claimEvents } from "../observable/claim-events.js";
|
|
5
5
|
import { claimLatest } from "../observable/claim-latest.js";
|
|
6
|
-
import { defined } from "../observable/defined.js";
|
|
7
6
|
/** Gets a single event from both types of event stores and returns an observable that completes */
|
|
8
7
|
function getEventFromStores(store, pointer) {
|
|
9
8
|
const r = store.getEvent(pointer.id);
|
|
@@ -35,10 +34,13 @@ function loadEventUsingFallback(store, pointer) {
|
|
|
35
34
|
return switchMap((event) => {
|
|
36
35
|
if (event)
|
|
37
36
|
return of(event);
|
|
38
|
-
// If
|
|
37
|
+
// If no loader pass value through, should never happen
|
|
39
38
|
if (!store.eventLoader)
|
|
40
|
-
return
|
|
41
|
-
|
|
39
|
+
return of(event);
|
|
40
|
+
// If event was not found, attempt to load
|
|
41
|
+
return from(store.eventLoader(pointer)).pipe(
|
|
42
|
+
// Start with `undefined` since its not loaded yet
|
|
43
|
+
startWith(undefined));
|
|
42
44
|
});
|
|
43
45
|
}
|
|
44
46
|
/** A model that returns a single event or undefined when its removed */
|
|
@@ -46,22 +48,22 @@ export function EventModel(pointer) {
|
|
|
46
48
|
if (typeof pointer === "string")
|
|
47
49
|
pointer = { id: pointer };
|
|
48
50
|
return (store) => merge(
|
|
49
|
-
// get current event
|
|
51
|
+
// get current event
|
|
50
52
|
defer(() => getEventFromStores(store, pointer)).pipe(
|
|
51
53
|
// If the event isn't found, attempt to load using the fallback loader
|
|
52
|
-
loadEventUsingFallback(store, pointer),
|
|
53
|
-
// Only emit found events
|
|
54
|
-
defined()),
|
|
54
|
+
store.eventLoader ? loadEventUsingFallback(store, pointer) : identity),
|
|
55
55
|
// Listen for new events
|
|
56
56
|
store.insert$.pipe(filter((e) => e.id === pointer.id)),
|
|
57
57
|
// emit undefined when deleted
|
|
58
|
-
store.remove$.pipe(filter((e) => e.id === pointer.id),
|
|
58
|
+
store.remove$.pipe(filter((e) => e.id === pointer.id),
|
|
59
|
+
// Complete when the event is removed
|
|
60
|
+
take(1),
|
|
59
61
|
// Emit undefined when deleted
|
|
60
|
-
|
|
62
|
+
map(() => undefined))).pipe(
|
|
63
|
+
// ignore duplicate events (true === same)
|
|
64
|
+
distinctUntilChanged((a, b) => a?.id === b?.id),
|
|
61
65
|
// claim all events
|
|
62
|
-
claimLatest(store)
|
|
63
|
-
// ignore duplicate events
|
|
64
|
-
distinctUntilChanged((a, b) => a?.id === b?.id));
|
|
66
|
+
claimLatest(store));
|
|
65
67
|
}
|
|
66
68
|
/** A model that returns the latest version of a replaceable event or undefined if its removed */
|
|
67
69
|
export function ReplaceableModel(pointer) {
|
|
@@ -71,26 +73,30 @@ export function ReplaceableModel(pointer) {
|
|
|
71
73
|
// lazily get current event
|
|
72
74
|
defer(() => getReplaceableFromStores(store, pointer)).pipe(
|
|
73
75
|
// If the event isn't found, attempt to load using the fallback loader
|
|
74
|
-
loadEventUsingFallback(store, pointer),
|
|
75
|
-
// Only emit found events
|
|
76
|
-
defined()),
|
|
76
|
+
store.eventLoader ? loadEventUsingFallback(store, pointer) : identity),
|
|
77
77
|
// Subscribe to new events that match the pointer
|
|
78
78
|
store.insert$.pipe(filter((e) => e.pubkey == pointer.pubkey &&
|
|
79
79
|
e.kind === pointer.kind &&
|
|
80
80
|
(pointer.identifier !== undefined ? getReplaceableIdentifier(e) === pointer.identifier : true)))).pipe(
|
|
81
|
-
//
|
|
81
|
+
// Hacky way to extract the current event so it can be used in the remove$ stream
|
|
82
|
+
tap((event) => (current = event)),
|
|
83
|
+
// Subscribe to the event being removed
|
|
84
|
+
mergeWith(store.remove$.pipe(filter((e) => {
|
|
85
|
+
return e.id === current?.id;
|
|
86
|
+
}),
|
|
87
|
+
// Emit undefined when the event is removed
|
|
88
|
+
map(() => {
|
|
89
|
+
return undefined;
|
|
90
|
+
}))),
|
|
91
|
+
// only update if event is newer (true === same)
|
|
82
92
|
distinctUntilChanged((prev, event) => {
|
|
83
|
-
//
|
|
84
|
-
|
|
93
|
+
// If the event has changed from undefined to defined or vice versa
|
|
94
|
+
if (prev === undefined || event === undefined) {
|
|
95
|
+
return prev === event;
|
|
96
|
+
}
|
|
97
|
+
// Return if event is newer than the previous event
|
|
98
|
+
return event.created_at < prev.created_at;
|
|
85
99
|
}),
|
|
86
|
-
// Hacky way to extract the current event so takeUntil can access it
|
|
87
|
-
tap((event) => (current = event)),
|
|
88
|
-
// complete when event is removed
|
|
89
|
-
takeUntil(store.remove$.pipe(filter((e) => e.id === current?.id))),
|
|
90
|
-
// emit undefined when removed
|
|
91
|
-
endWith(undefined),
|
|
92
|
-
// keep the observable hot
|
|
93
|
-
repeat(),
|
|
94
100
|
// claim latest event
|
|
95
101
|
claimLatest(store));
|
|
96
102
|
};
|
|
@@ -128,7 +134,8 @@ export function TimelineModel(filters, includeOldVersion) {
|
|
|
128
134
|
if (isReplaceable(e.kind))
|
|
129
135
|
seen.set(getEventUID(e), e);
|
|
130
136
|
}
|
|
131
|
-
return
|
|
137
|
+
// Always return a new array instance to ensure UI libraries detect changes
|
|
138
|
+
return [...event];
|
|
132
139
|
}
|
|
133
140
|
// create a new timeline and insert the event into it
|
|
134
141
|
let newTimeline = [...timeline];
|
|
@@ -136,14 +143,17 @@ export function TimelineModel(filters, includeOldVersion) {
|
|
|
136
143
|
if (!includeOldVersion && isReplaceable(event.kind)) {
|
|
137
144
|
const uid = getEventUID(event);
|
|
138
145
|
const existing = seen.get(uid);
|
|
139
|
-
// if this is an older replaceable event,
|
|
146
|
+
// if this is an older replaceable event, return a new array instance
|
|
140
147
|
if (existing && event.created_at < existing.created_at)
|
|
141
|
-
return timeline;
|
|
148
|
+
return [...timeline];
|
|
142
149
|
// update latest version
|
|
143
150
|
seen.set(uid, event);
|
|
144
151
|
// remove old event from timeline
|
|
145
|
-
if (existing)
|
|
146
|
-
newTimeline.
|
|
152
|
+
if (existing) {
|
|
153
|
+
const index = newTimeline.indexOf(existing);
|
|
154
|
+
if (index !== -1)
|
|
155
|
+
newTimeline.splice(index, 1);
|
|
156
|
+
}
|
|
147
157
|
}
|
|
148
158
|
// add event into timeline
|
|
149
159
|
insertEventIntoDescendingList(newTimeline, event);
|
|
@@ -5,3 +5,5 @@ import { IEventStoreStreams } from "../event-store/interface.js";
|
|
|
5
5
|
export declare function watchEventUpdates(eventStore: IEventStoreStreams): MonoTypeOperatorFunction<NostrEvent | undefined>;
|
|
6
6
|
/** Watches for any updates to the latest array of events and remits the array of events when updated */
|
|
7
7
|
export declare function watchEventsUpdates(eventStore: IEventStoreStreams): MonoTypeOperatorFunction<NostrEvent[]>;
|
|
8
|
+
/** Alias for watchEventsUpdates */
|
|
9
|
+
export declare const watchTimelineUpdates: typeof watchEventsUpdates;
|
|
@@ -5,7 +5,7 @@ import { AddressPointer, EventPointer, ProfilePointer } from "../../helpers/poin
|
|
|
5
5
|
export declare function addProfilePointerTag(pubkey: string | ProfilePointer, replace?: boolean): TagOperation;
|
|
6
6
|
/** Removes all "p" tags matching a pubkey */
|
|
7
7
|
export declare function removeProfilePointerTag(pubkey: string | ProfilePointer): TagOperation;
|
|
8
|
-
/** Adds a
|
|
8
|
+
/** Adds a single "e" tag for an EventPointer */
|
|
9
9
|
export declare function addEventPointerTag(id: string | EventPointer | NostrEvent, replace?: boolean): TagOperation;
|
|
10
10
|
/** Removes all "e" tags matching EventPointer or id */
|
|
11
11
|
export declare function removeEventPointerTag(id: string | EventPointer): TagOperation;
|
|
@@ -24,7 +24,7 @@ export function removeProfilePointerTag(pubkey) {
|
|
|
24
24
|
pubkey = typeof pubkey !== "string" ? pubkey.pubkey : pubkey;
|
|
25
25
|
return (tags) => tags.filter((t) => !(t[0] === "p" && t[1] === pubkey));
|
|
26
26
|
}
|
|
27
|
-
/** Adds a
|
|
27
|
+
/** Adds a single "e" tag for an EventPointer */
|
|
28
28
|
export function addEventPointerTag(id, replace = true) {
|
|
29
29
|
return async (tags, { getEventRelayHint }) => {
|
|
30
30
|
const pointer = typeof id === "string" ? { id } : isEvent(id) ? getEventPointerForEvent(id) : id;
|