applesauce-core 0.0.0-next-20251205152544 → 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-delete-manager.d.ts +26 -0
- package/dist/event-store/async-delete-manager.js +33 -0
- package/dist/event-store/async-event-store.d.ts +32 -24
- package/dist/event-store/async-event-store.js +130 -164
- package/dist/event-store/delete-manager.d.ts +30 -0
- package/dist/event-store/delete-manager.js +99 -0
- 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 +33 -23
- package/dist/event-store/event-store.js +151 -162
- package/dist/event-store/expiration-manager.d.ts +37 -0
- package/dist/event-store/expiration-manager.js +97 -0
- package/dist/event-store/index.d.ts +3 -0
- package/dist/event-store/index.js +3 -0
- package/dist/event-store/interface.d.ts +73 -6
- package/dist/helpers/contacts.d.ts +1 -0
- package/dist/helpers/contacts.js +5 -5
- package/dist/helpers/delete.d.ts +8 -1
- package/dist/helpers/delete.js +25 -1
- package/dist/helpers/encrypted-content.d.ts +3 -1
- package/dist/helpers/event-cache.js +1 -1
- package/dist/helpers/event.d.ts +4 -4
- package/dist/helpers/event.js +5 -5
- package/dist/helpers/factory.js +4 -4
- 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/index.d.ts +1 -1
- package/dist/helpers/index.js +1 -1
- package/dist/helpers/model.d.ts +5 -0
- package/dist/helpers/model.js +17 -0
- package/dist/helpers/pointers.d.ts +19 -38
- package/dist/helpers/pointers.js +78 -84
- 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/string.js +8 -2
- package/dist/helpers/tags.d.ts +3 -1
- package/dist/helpers/tags.js +5 -1
- package/dist/models/base.js +43 -33
- package/dist/models/index.d.ts +0 -1
- package/dist/models/index.js +0 -1
- package/dist/observable/watch-event-updates.d.ts +2 -0
- package/dist/observable/watch-event-updates.js +2 -0
- package/dist/operations/client.js +2 -2
- package/dist/operations/delete.js +5 -3
- package/dist/operations/tag/common.d.ts +5 -4
- package/dist/operations/tag/common.js +24 -11
- package/package.json +6 -1
- package/dist/helpers/lists.d.ts +0 -58
- package/dist/helpers/lists.js +0 -110
- package/dist/models/relays.d.ts +0 -27
- package/dist/models/relays.js +0 -36
|
@@ -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
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Observable } from "rxjs";
|
|
2
|
+
import { NostrEvent } from "../helpers/event.js";
|
|
3
|
+
import { DeleteEventNotification, IAsyncDeleteManager } from "./interface.js";
|
|
4
|
+
/** Async manager for deletion state, ensuring users can only delete their own events */
|
|
5
|
+
export declare class AsyncDeleteManager implements IAsyncDeleteManager {
|
|
6
|
+
/** A stream of pointers that may have been deleted */
|
|
7
|
+
readonly deleted$: Observable<DeleteEventNotification>;
|
|
8
|
+
/** Internal sync delete manager instance for state */
|
|
9
|
+
private internal;
|
|
10
|
+
constructor();
|
|
11
|
+
/**
|
|
12
|
+
* Process a kind 5 delete event
|
|
13
|
+
* Extracts event pointers and address pointers from the delete event
|
|
14
|
+
* Enforces that users can only delete their own events
|
|
15
|
+
*/
|
|
16
|
+
add(deleteEvent: NostrEvent): Promise<DeleteEventNotification[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Check if an event is deleted
|
|
19
|
+
* Verifies the event was deleted by its own author
|
|
20
|
+
*/
|
|
21
|
+
check(event: NostrEvent): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Filter out all deleted events from an array of events
|
|
24
|
+
*/
|
|
25
|
+
filter(events: NostrEvent[]): Promise<NostrEvent[]>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DeleteManager } from "./delete-manager.js";
|
|
2
|
+
/** Async manager for deletion state, ensuring users can only delete their own events */
|
|
3
|
+
export class AsyncDeleteManager {
|
|
4
|
+
/** A stream of pointers that may have been deleted */
|
|
5
|
+
deleted$;
|
|
6
|
+
/** Internal sync delete manager instance for state */
|
|
7
|
+
internal;
|
|
8
|
+
constructor() {
|
|
9
|
+
this.internal = new DeleteManager();
|
|
10
|
+
this.deleted$ = this.internal.deleted$;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Process a kind 5 delete event
|
|
14
|
+
* Extracts event pointers and address pointers from the delete event
|
|
15
|
+
* Enforces that users can only delete their own events
|
|
16
|
+
*/
|
|
17
|
+
async add(deleteEvent) {
|
|
18
|
+
return this.internal.add(deleteEvent);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if an event is deleted
|
|
22
|
+
* Verifies the event was deleted by its own author
|
|
23
|
+
*/
|
|
24
|
+
async check(event) {
|
|
25
|
+
return this.internal.check(event);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Filter out all deleted events from an array of events
|
|
29
|
+
*/
|
|
30
|
+
async filter(events) {
|
|
31
|
+
return this.internal.filter(events);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -4,16 +4,38 @@ 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 } from "./interface.js";
|
|
7
|
+
import { IAsyncDeleteManager, IAsyncEventDatabase, IAsyncEventStore, IExpirationManager } from "./interface.js";
|
|
8
|
+
export type AsyncEventStoreOptions = {
|
|
9
|
+
/** Keep deleted events in the store */
|
|
10
|
+
keepDeleted?: boolean;
|
|
11
|
+
/** Keep expired events in the store */
|
|
12
|
+
keepExpired?: boolean;
|
|
13
|
+
/** Enable this to keep old versions of replaceable events */
|
|
14
|
+
keepOldVersions?: boolean;
|
|
15
|
+
/** The database to use for storing events */
|
|
16
|
+
database: IAsyncEventDatabase;
|
|
17
|
+
/** Custom {@link IAsyncDeleteManager} implementation */
|
|
18
|
+
deleteManager?: IAsyncDeleteManager;
|
|
19
|
+
/** Custom {@link IExpirationManager} implementation */
|
|
20
|
+
expirationManager?: IExpirationManager;
|
|
21
|
+
/** The method used to verify events */
|
|
22
|
+
verifyEvent?: (event: NostrEvent) => boolean;
|
|
23
|
+
};
|
|
8
24
|
/** An async wrapper around an async event database that handles replaceable events, deletes, and models */
|
|
9
25
|
export declare class AsyncEventStore extends EventModels implements IAsyncEventStore {
|
|
10
26
|
database: IAsyncEventDatabase;
|
|
11
27
|
/** Optional memory database for ensuring single event instances */
|
|
12
28
|
memory: EventMemory;
|
|
29
|
+
/** Manager for handling event deletions with authorization */
|
|
30
|
+
private deletes;
|
|
31
|
+
/** Manager for handling event expirations */
|
|
32
|
+
private expiration;
|
|
13
33
|
/** Enable this to keep old versions of replaceable events */
|
|
14
34
|
keepOldVersions: boolean;
|
|
15
|
-
/**
|
|
35
|
+
/** Keep expired events in the store */
|
|
16
36
|
keepExpired: boolean;
|
|
37
|
+
/** Keep deleted events in the store */
|
|
38
|
+
keepDeleted: boolean;
|
|
17
39
|
/** The method used to verify events */
|
|
18
40
|
private _verifyEventMethod?;
|
|
19
41
|
/** Get the method used to verify events */
|
|
@@ -22,7 +44,7 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
|
|
|
22
44
|
set verifyEvent(method: undefined | ((event: NostrEvent) => boolean));
|
|
23
45
|
/** A stream of new events added to the store */
|
|
24
46
|
insert$: Subject<import("nostr-tools/core").Event>;
|
|
25
|
-
/** 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) */
|
|
26
48
|
update$: Subject<import("nostr-tools/core").Event>;
|
|
27
49
|
/** A stream of events that have been removed */
|
|
28
50
|
remove$: Subject<import("nostr-tools/core").Event>;
|
|
@@ -30,23 +52,13 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
|
|
|
30
52
|
* A method that will be called when an event isn't found in the store
|
|
31
53
|
*/
|
|
32
54
|
eventLoader?: (pointer: EventPointer | AddressPointer | AddressPointerWithoutD) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
|
|
33
|
-
constructor(
|
|
55
|
+
constructor(options: AsyncEventStoreOptions);
|
|
34
56
|
/** A method to add all events to memory to ensure there is only ever a single instance of an event */
|
|
35
57
|
private mapToMemory;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/** Adds an event to the expiration map */
|
|
41
|
-
protected addExpiration(event: NostrEvent): void;
|
|
42
|
-
protected expirationTimeout: number | null;
|
|
43
|
-
protected nextExpirationCheck: number | null;
|
|
44
|
-
protected handleExpiringEvent(event: NostrEvent): void;
|
|
45
|
-
/** Remove expired events from the store */
|
|
46
|
-
protected pruneExpired(): Promise<void>;
|
|
47
|
-
protected handleDeleteEvent(deleteEvent: NostrEvent): Promise<void>;
|
|
48
|
-
/** Copies important metadata from and identical event to another */
|
|
49
|
-
static mergeDuplicateEvent(source: NostrEvent, dest: NostrEvent): void;
|
|
58
|
+
/** Handle a delete event by pointer */
|
|
59
|
+
private handleDeleteNotification;
|
|
60
|
+
/** Handle an expired event by id */
|
|
61
|
+
private handleExpiredNotification;
|
|
50
62
|
/**
|
|
51
63
|
* Adds an event to the store and update subscriptions
|
|
52
64
|
* @returns The existing event or the event that was added, if it was ignored returns null
|
|
@@ -59,9 +71,9 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
|
|
|
59
71
|
/** Add an event to the store and notifies all subscribes it has updated */
|
|
60
72
|
update(event: NostrEvent): Promise<void>;
|
|
61
73
|
/** Check if the store has an event by id */
|
|
62
|
-
hasEvent(id: string): Promise<boolean>;
|
|
74
|
+
hasEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): Promise<boolean>;
|
|
63
75
|
/** Get an event by id from the store */
|
|
64
|
-
getEvent(id: string): Promise<NostrEvent | undefined>;
|
|
76
|
+
getEvent(id: string | EventPointer | AddressPointer | AddressPointerWithoutD): Promise<NostrEvent | undefined>;
|
|
65
77
|
/** Check if the store has a replaceable event */
|
|
66
78
|
hasReplaceable(kind: number, pubkey: string, d?: string): Promise<boolean>;
|
|
67
79
|
/** Gets the latest version of a replaceable event */
|
|
@@ -86,8 +98,4 @@ export declare class AsyncEventStore extends EventModels implements IAsyncEventS
|
|
|
86
98
|
unclaimed(): Generator<NostrEvent>;
|
|
87
99
|
/** Removes any event that is not being used by a subscription */
|
|
88
100
|
prune(limit?: number): number;
|
|
89
|
-
/** Returns an observable that completes when an event is removed */
|
|
90
|
-
removed(id: string): Observable<never>;
|
|
91
|
-
/** Creates an observable that emits when event is updated */
|
|
92
|
-
updated(event: string | NostrEvent): Observable<NostrEvent>;
|
|
93
101
|
}
|
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { verifyEvent as coreVerifyEvent } from "nostr-tools/pure";
|
|
2
|
+
import { Subject } from "rxjs";
|
|
3
|
+
import { EventStoreSymbol, getReplaceableIdentifier, isReplaceable, kinds } from "../helpers/event.js";
|
|
4
4
|
import { getExpirationTimestamp } from "../helpers/expiration.js";
|
|
5
|
-
import {
|
|
6
|
-
import { addSeenRelay
|
|
5
|
+
import { eventMatchesPointer, isAddressPointer, isEventPointer, } from "../helpers/pointers.js";
|
|
6
|
+
import { addSeenRelay } from "../helpers/relays.js";
|
|
7
7
|
import { unixNow } from "../helpers/time.js";
|
|
8
|
+
import { AsyncDeleteManager } from "./async-delete-manager.js";
|
|
8
9
|
import { EventMemory } from "./event-memory.js";
|
|
9
10
|
import { EventModels } from "./event-models.js";
|
|
10
|
-
import {
|
|
11
|
+
import { EventStore } from "./event-store.js";
|
|
12
|
+
import { ExpirationManager } from "./expiration-manager.js";
|
|
11
13
|
/** An async wrapper around an async event database that handles replaceable events, deletes, and models */
|
|
12
14
|
export class AsyncEventStore extends EventModels {
|
|
13
15
|
database;
|
|
14
16
|
/** Optional memory database for ensuring single event instances */
|
|
15
17
|
memory;
|
|
18
|
+
/** Manager for handling event deletions with authorization */
|
|
19
|
+
deletes;
|
|
20
|
+
/** Manager for handling event expirations */
|
|
21
|
+
expiration;
|
|
16
22
|
/** Enable this to keep old versions of replaceable events */
|
|
17
23
|
keepOldVersions = false;
|
|
18
|
-
/**
|
|
24
|
+
/** Keep expired events in the store */
|
|
19
25
|
keepExpired = false;
|
|
26
|
+
/** Keep deleted events in the store */
|
|
27
|
+
keepDeleted = false;
|
|
20
28
|
/** The method used to verify events */
|
|
21
29
|
_verifyEventMethod = coreVerifyEvent;
|
|
22
30
|
/** Get the method used to verify events */
|
|
@@ -26,13 +34,12 @@ export class AsyncEventStore extends EventModels {
|
|
|
26
34
|
/** Sets the method used to verify events */
|
|
27
35
|
set verifyEvent(method) {
|
|
28
36
|
this._verifyEventMethod = method;
|
|
29
|
-
if (method === undefined)
|
|
37
|
+
if (method === undefined)
|
|
30
38
|
console.warn("[applesauce-core] AsyncEventStore.verifyEvent is undefined; signature checks are disabled.");
|
|
31
|
-
}
|
|
32
39
|
}
|
|
33
40
|
/** A stream of new events added to the store */
|
|
34
41
|
insert$ = new Subject();
|
|
35
|
-
/** 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) */
|
|
36
43
|
update$ = new Subject();
|
|
37
44
|
/** A stream of events that have been removed */
|
|
38
45
|
remove$ = new Subject();
|
|
@@ -40,17 +47,34 @@ export class AsyncEventStore extends EventModels {
|
|
|
40
47
|
* A method that will be called when an event isn't found in the store
|
|
41
48
|
*/
|
|
42
49
|
eventLoader;
|
|
43
|
-
constructor(
|
|
50
|
+
constructor(options) {
|
|
44
51
|
super();
|
|
45
|
-
this.database = database;
|
|
52
|
+
this.database = options.database;
|
|
46
53
|
this.memory = new EventMemory();
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
// Set options if provided
|
|
55
|
+
if (options.keepDeleted !== undefined)
|
|
56
|
+
this.keepDeleted = options.keepDeleted;
|
|
57
|
+
if (options.keepExpired !== undefined)
|
|
58
|
+
this.keepExpired = options.keepExpired;
|
|
59
|
+
if (options.keepOldVersions !== undefined)
|
|
60
|
+
this.keepOldVersions = options.keepOldVersions;
|
|
61
|
+
if (options.verifyEvent)
|
|
62
|
+
this.verifyEvent = options.verifyEvent;
|
|
63
|
+
// Use provided delete manager or create a default one
|
|
64
|
+
this.deletes = options.deleteManager ?? new AsyncDeleteManager();
|
|
65
|
+
// Listen to delete notifications and remove matching events
|
|
66
|
+
this.deletes.deleted$.subscribe((notification) => {
|
|
67
|
+
this.handleDeleteNotification(notification).catch((error) => {
|
|
68
|
+
console.error("[applesauce-core] Error handling delete notification:", error);
|
|
69
|
+
});
|
|
50
70
|
});
|
|
51
|
-
//
|
|
52
|
-
this.
|
|
53
|
-
|
|
71
|
+
// Create expiration manager
|
|
72
|
+
this.expiration = options.expirationManager ?? new ExpirationManager();
|
|
73
|
+
// Listen to expired events and remove them from the store
|
|
74
|
+
this.expiration.expired$.subscribe((id) => {
|
|
75
|
+
this.handleExpiredNotification(id).catch((error) => {
|
|
76
|
+
console.error("[applesauce-core] Error handling expired notification:", error);
|
|
77
|
+
});
|
|
54
78
|
});
|
|
55
79
|
}
|
|
56
80
|
mapToMemory(event) {
|
|
@@ -60,100 +84,36 @@ export class AsyncEventStore extends EventModels {
|
|
|
60
84
|
return event;
|
|
61
85
|
return this.memory.add(event);
|
|
62
86
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (typeof event === "string")
|
|
68
|
-
return this.deletedIds.has(event);
|
|
69
|
-
else {
|
|
70
|
-
if (this.deletedIds.has(event.id))
|
|
71
|
-
return true;
|
|
72
|
-
if (isAddressableKind(event.kind)) {
|
|
73
|
-
const identifier = event.tags.find((t) => t[0] === "d")?.[1];
|
|
74
|
-
const deleted = this.deletedCoords.get(createReplaceableAddress(event.kind, event.pubkey, identifier));
|
|
75
|
-
if (deleted)
|
|
76
|
-
return deleted > event.created_at;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
expirations = new Map();
|
|
82
|
-
/** Adds an event to the expiration map */
|
|
83
|
-
addExpiration(event) {
|
|
84
|
-
const expiration = getExpirationTimestamp(event);
|
|
85
|
-
if (expiration && Number.isFinite(expiration))
|
|
86
|
-
this.expirations.set(event.id, expiration);
|
|
87
|
-
}
|
|
88
|
-
expirationTimeout = null;
|
|
89
|
-
nextExpirationCheck = null;
|
|
90
|
-
handleExpiringEvent(event) {
|
|
91
|
-
const expiration = getExpirationTimestamp(event);
|
|
92
|
-
if (!expiration)
|
|
87
|
+
/** Handle a delete event by pointer */
|
|
88
|
+
async handleDeleteNotification({ pointer, until }) {
|
|
89
|
+
// Skip if keeping deleted events
|
|
90
|
+
if (this.keepDeleted)
|
|
93
91
|
return;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Set timeout to prune expired events
|
|
100
|
-
if (this.expirationTimeout)
|
|
101
|
-
clearTimeout(this.expirationTimeout);
|
|
102
|
-
const timeout = expiration - unixNow();
|
|
103
|
-
this.expirationTimeout = setTimeout(this.pruneExpired.bind(this), timeout * 1000 + 10);
|
|
104
|
-
this.nextExpirationCheck = expiration;
|
|
105
|
-
}
|
|
106
|
-
/** Remove expired events from the store */
|
|
107
|
-
async pruneExpired() {
|
|
108
|
-
const now = unixNow();
|
|
109
|
-
for (const [id, expiration] of this.expirations) {
|
|
110
|
-
if (expiration <= now) {
|
|
111
|
-
this.expirations.delete(id);
|
|
112
|
-
await this.remove(id);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// Cleanup timers
|
|
116
|
-
if (this.expirationTimeout)
|
|
117
|
-
clearTimeout(this.expirationTimeout);
|
|
118
|
-
this.nextExpirationCheck = null;
|
|
119
|
-
this.expirationTimeout = null;
|
|
120
|
-
}
|
|
121
|
-
// handling delete events
|
|
122
|
-
async handleDeleteEvent(deleteEvent) {
|
|
123
|
-
const ids = getDeleteIds(deleteEvent);
|
|
124
|
-
for (const id of ids) {
|
|
125
|
-
this.deletedIds.add(id);
|
|
126
|
-
// remove deleted events in the database
|
|
127
|
-
await this.remove(id);
|
|
92
|
+
if (isEventPointer(pointer)) {
|
|
93
|
+
// For event pointers, get the event by ID and remove if it exists
|
|
94
|
+
const event = await this.getEvent(pointer.id);
|
|
95
|
+
if (event && until >= event.created_at && eventMatchesPointer(event, pointer))
|
|
96
|
+
await this.remove(event);
|
|
128
97
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.
|
|
132
|
-
// Parse the nostr address coordinate
|
|
133
|
-
const parsed = parseCoordinate(coord);
|
|
134
|
-
if (!parsed)
|
|
135
|
-
continue;
|
|
136
|
-
// Remove older versions of replaceable events
|
|
137
|
-
const events = await this.database.getReplaceableHistory(parsed.kind, parsed.pubkey, parsed.identifier);
|
|
98
|
+
else if (isAddressPointer(pointer)) {
|
|
99
|
+
// For address pointers, get all events matching the address and remove if deleted
|
|
100
|
+
const events = await this.getReplaceableHistory(pointer.kind, pointer.pubkey, pointer.identifier);
|
|
138
101
|
if (events) {
|
|
139
102
|
for (const event of events) {
|
|
140
|
-
|
|
103
|
+
// Remove the event if its older than the delete notification and matches the pointer
|
|
104
|
+
if (until >= event.created_at && eventMatchesPointer(event, pointer)) {
|
|
141
105
|
await this.remove(event);
|
|
106
|
+
}
|
|
142
107
|
}
|
|
143
108
|
}
|
|
144
109
|
}
|
|
145
110
|
}
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
// copy the from cache symbol only if its true
|
|
154
|
-
const fromCache = Reflect.get(source, FromCacheSymbol);
|
|
155
|
-
if (fromCache && !Reflect.get(dest, FromCacheSymbol))
|
|
156
|
-
Reflect.set(dest, FromCacheSymbol, fromCache);
|
|
111
|
+
/** Handle an expired event by id */
|
|
112
|
+
async handleExpiredNotification(id) {
|
|
113
|
+
// Skip if keeping expired events
|
|
114
|
+
if (this.keepExpired)
|
|
115
|
+
return;
|
|
116
|
+
await this.remove(id);
|
|
157
117
|
}
|
|
158
118
|
/**
|
|
159
119
|
* Adds an event to the store and update subscriptions
|
|
@@ -161,23 +121,29 @@ export class AsyncEventStore extends EventModels {
|
|
|
161
121
|
*/
|
|
162
122
|
async add(event, fromRelay) {
|
|
163
123
|
// Handle delete events differently
|
|
164
|
-
if (event.kind === kinds.EventDeletion)
|
|
165
|
-
await this.
|
|
124
|
+
if (event.kind === kinds.EventDeletion) {
|
|
125
|
+
await this.deletes.add(event);
|
|
126
|
+
return event;
|
|
127
|
+
}
|
|
166
128
|
// Ignore if the event was deleted
|
|
167
|
-
if (this.
|
|
129
|
+
if (await this.deletes.check(event))
|
|
168
130
|
return event;
|
|
169
131
|
// Reject expired events if keepExpired is false
|
|
170
132
|
const expiration = getExpirationTimestamp(event);
|
|
171
133
|
if (this.keepExpired === false && expiration && expiration <= unixNow())
|
|
172
134
|
return null;
|
|
135
|
+
// Attach relay this event was from
|
|
136
|
+
if (fromRelay)
|
|
137
|
+
addSeenRelay(event, fromRelay);
|
|
173
138
|
// Get the replaceable identifier
|
|
174
|
-
const identifier = isReplaceable(event.kind) ? event
|
|
139
|
+
const identifier = isReplaceable(event.kind) ? getReplaceableIdentifier(event) : undefined;
|
|
175
140
|
// Don't insert the event if there is already a newer version
|
|
176
|
-
if (
|
|
141
|
+
if (this.keepOldVersions === false && isReplaceable(event.kind)) {
|
|
177
142
|
const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
|
|
178
143
|
// If there is already a newer version, copy cached symbols and return existing event
|
|
179
144
|
if (existing && existing.length > 0 && existing[0].created_at >= event.created_at) {
|
|
180
|
-
|
|
145
|
+
if (EventStore.copySymbolsToDuplicateEvent(event, existing[0]))
|
|
146
|
+
await this.update(existing[0]);
|
|
181
147
|
return existing[0];
|
|
182
148
|
}
|
|
183
149
|
}
|
|
@@ -185,29 +151,30 @@ export class AsyncEventStore extends EventModels {
|
|
|
185
151
|
if (this.verifyEvent && this.verifyEvent(event) === false)
|
|
186
152
|
return null;
|
|
187
153
|
// Always add event to memory
|
|
188
|
-
const existing = this.memory
|
|
154
|
+
const existing = this.memory.add(event);
|
|
189
155
|
// If the memory returned a different instance, this is a duplicate event
|
|
190
156
|
if (existing && existing !== event) {
|
|
191
157
|
// Copy cached symbols and return existing event
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (fromRelay)
|
|
195
|
-
addSeenRelay(existing, fromRelay);
|
|
158
|
+
if (EventStore.copySymbolsToDuplicateEvent(event, existing))
|
|
159
|
+
await this.update(existing);
|
|
196
160
|
return existing;
|
|
197
161
|
}
|
|
198
162
|
// Insert event into database
|
|
199
163
|
const inserted = this.mapToMemory(await this.database.add(event));
|
|
200
|
-
//
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
addSeenRelay(inserted, fromRelay);
|
|
206
|
-
// Emit insert$ signal
|
|
207
|
-
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
|
|
208
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
|
+
}
|
|
209
176
|
// remove all old version of the replaceable event
|
|
210
|
-
if (
|
|
177
|
+
if (this.keepOldVersions === false && isReplaceable(event.kind)) {
|
|
211
178
|
const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
|
|
212
179
|
if (existing && existing.length > 0) {
|
|
213
180
|
const older = Array.from(existing).filter((e) => e.created_at < event.created_at);
|
|
@@ -219,29 +186,37 @@ export class AsyncEventStore extends EventModels {
|
|
|
219
186
|
return existing[0];
|
|
220
187
|
}
|
|
221
188
|
}
|
|
222
|
-
// Add event to expiration
|
|
223
|
-
if (this.keepExpired === false && expiration)
|
|
224
|
-
this.
|
|
189
|
+
// Add event to expiration manager if it has an expiration tag
|
|
190
|
+
if (this.keepExpired === false && expiration !== undefined)
|
|
191
|
+
this.expiration.track(inserted);
|
|
225
192
|
return inserted;
|
|
226
193
|
}
|
|
227
194
|
/** Removes an event from the store and updates subscriptions */
|
|
228
195
|
async remove(event) {
|
|
229
|
-
|
|
196
|
+
const eventId = typeof event === "string" ? event : event.id;
|
|
197
|
+
let instance = this.memory.getEvent(eventId);
|
|
198
|
+
// Remove from expiration manager
|
|
199
|
+
this.expiration.forget(eventId);
|
|
200
|
+
// Remove the event store from the event
|
|
201
|
+
if (instance)
|
|
202
|
+
Reflect.deleteProperty(instance, EventStoreSymbol);
|
|
230
203
|
// Remove from memory if available
|
|
231
204
|
if (this.memory)
|
|
232
205
|
this.memory.remove(event);
|
|
233
206
|
// Remove the event from the database
|
|
234
207
|
const removed = await this.database.remove(event);
|
|
235
208
|
// If the event was removed, notify the subscriptions
|
|
236
|
-
if (removed && instance)
|
|
209
|
+
if (removed && instance)
|
|
237
210
|
this.remove$.next(instance);
|
|
238
|
-
}
|
|
239
211
|
return removed;
|
|
240
212
|
}
|
|
241
213
|
/** Remove multiple events that match the given filters */
|
|
242
214
|
async removeByFilters(filters) {
|
|
243
215
|
// Get events that will be removed for notification
|
|
244
216
|
const eventsToRemove = await this.getByFilters(filters);
|
|
217
|
+
// Remove from expiration manager
|
|
218
|
+
for (const event of eventsToRemove)
|
|
219
|
+
this.expiration.forget(event.id);
|
|
245
220
|
// Remove from memory if available
|
|
246
221
|
if (this.memory)
|
|
247
222
|
this.memory.removeByFilters(filters);
|
|
@@ -265,33 +240,41 @@ export class AsyncEventStore extends EventModels {
|
|
|
265
240
|
}
|
|
266
241
|
/** Check if the store has an event by id */
|
|
267
242
|
async hasEvent(id) {
|
|
268
|
-
|
|
269
|
-
|
|
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);
|
|
270
250
|
}
|
|
271
251
|
/** Get an event by id from the store */
|
|
272
252
|
async getEvent(id) {
|
|
273
253
|
// Get the event from memory first, then from the database
|
|
274
|
-
|
|
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);
|
|
275
261
|
}
|
|
276
262
|
/** Check if the store has a replaceable event */
|
|
277
263
|
async hasReplaceable(kind, pubkey, d) {
|
|
278
264
|
// Check if the event exists in memory first, then in the database
|
|
279
|
-
return
|
|
265
|
+
return this.memory.hasReplaceable(kind, pubkey, d) || this.database.hasReplaceable(kind, pubkey, d);
|
|
280
266
|
}
|
|
281
267
|
/** Gets the latest version of a replaceable event */
|
|
282
268
|
async getReplaceable(kind, pubkey, identifier) {
|
|
283
269
|
// Get the event from memory first, then from the database
|
|
284
|
-
return (this.memory
|
|
270
|
+
return (this.memory.getReplaceable(kind, pubkey, identifier) ??
|
|
285
271
|
this.mapToMemory(await this.database.getReplaceable(kind, pubkey, identifier)));
|
|
286
272
|
}
|
|
287
273
|
/** Returns all versions of a replaceable event */
|
|
288
274
|
async getReplaceableHistory(kind, pubkey, identifier) {
|
|
289
275
|
// Get the events from memory first, then from the database
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
return memoryEvents;
|
|
293
|
-
const dbEvents = await this.database.getReplaceableHistory(kind, pubkey, identifier);
|
|
294
|
-
return dbEvents?.map((e) => this.mapToMemory(e) ?? e);
|
|
276
|
+
return (this.memory.getReplaceableHistory(kind, pubkey, identifier) ??
|
|
277
|
+
(await this.database.getReplaceableHistory(kind, pubkey, identifier))?.map((e) => this.mapToMemory(e) ?? e));
|
|
295
278
|
}
|
|
296
279
|
/** Get all events matching a filter */
|
|
297
280
|
async getByFilters(filters) {
|
|
@@ -299,7 +282,7 @@ export class AsyncEventStore extends EventModels {
|
|
|
299
282
|
const events = await this.database.getByFilters(filters);
|
|
300
283
|
// Map events to memory if available for better performance
|
|
301
284
|
if (this.memory)
|
|
302
|
-
return events.map((e) => this.mapToMemory(e)
|
|
285
|
+
return events.map((e) => this.mapToMemory(e));
|
|
303
286
|
else
|
|
304
287
|
return events;
|
|
305
288
|
}
|
|
@@ -313,47 +296,30 @@ export class AsyncEventStore extends EventModels {
|
|
|
313
296
|
}
|
|
314
297
|
/** Passthrough method for the database.touch */
|
|
315
298
|
touch(event) {
|
|
316
|
-
return this.memory
|
|
299
|
+
return this.memory.touch(event);
|
|
317
300
|
}
|
|
318
301
|
/** Increments the claim count on the event and touches it */
|
|
319
302
|
claim(event) {
|
|
320
|
-
return this.memory
|
|
303
|
+
return this.memory.claim(event);
|
|
321
304
|
}
|
|
322
305
|
/** Checks if an event is claimed by anything */
|
|
323
306
|
isClaimed(event) {
|
|
324
|
-
return this.memory
|
|
307
|
+
return this.memory.isClaimed(event) ?? false;
|
|
325
308
|
}
|
|
326
309
|
/** Decrements the claim count on an event */
|
|
327
310
|
removeClaim(event) {
|
|
328
|
-
return this.memory
|
|
311
|
+
return this.memory.removeClaim(event);
|
|
329
312
|
}
|
|
330
313
|
/** Removes all claims on an event */
|
|
331
314
|
clearClaim(event) {
|
|
332
|
-
return this.memory
|
|
315
|
+
return this.memory.clearClaim(event);
|
|
333
316
|
}
|
|
334
317
|
/** Pass through method for the database.unclaimed */
|
|
335
318
|
unclaimed() {
|
|
336
|
-
return this.memory
|
|
319
|
+
return this.memory.unclaimed() || (function* () { })();
|
|
337
320
|
}
|
|
338
321
|
/** Removes any event that is not being used by a subscription */
|
|
339
322
|
prune(limit) {
|
|
340
|
-
return this.memory
|
|
341
|
-
}
|
|
342
|
-
/** Returns an observable that completes when an event is removed */
|
|
343
|
-
removed(id) {
|
|
344
|
-
const deleted = this.checkDeleted(id);
|
|
345
|
-
if (deleted)
|
|
346
|
-
return EMPTY;
|
|
347
|
-
return this.remove$.pipe(
|
|
348
|
-
// listen for removed events
|
|
349
|
-
filter((e) => e.id === id),
|
|
350
|
-
// complete as soon as we find a matching removed event
|
|
351
|
-
take(1),
|
|
352
|
-
// switch to empty
|
|
353
|
-
mergeMap(() => EMPTY));
|
|
354
|
-
}
|
|
355
|
-
/** Creates an observable that emits when event is updated */
|
|
356
|
-
updated(event) {
|
|
357
|
-
return this.update$.pipe(filter((e) => e.id === event || e === event));
|
|
323
|
+
return this.memory.prune(limit) ?? 0;
|
|
358
324
|
}
|
|
359
325
|
}
|