applesauce-core 0.0.0-next-20250912090040 → 0.0.0-next-20250913205403
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/event-store/async-event-store.d.ts +129 -0
- package/dist/event-store/async-event-store.js +323 -0
- package/dist/event-store/event-database.d.ts +74 -0
- package/dist/event-store/event-database.js +339 -0
- package/dist/event-store/event-set.d.ts +0 -9
- package/dist/event-store/event-set.js +0 -18
- package/dist/event-store/event-store.d.ts +50 -61
- package/dist/event-store/event-store.js +63 -170
- package/dist/event-store/index.d.ts +2 -1
- package/dist/event-store/index.js +2 -1
- package/dist/event-store/interface.d.ts +109 -37
- package/dist/event-store/model-mixin.d.ts +59 -0
- package/dist/event-store/model-mixin.js +147 -0
- package/dist/helpers/event.d.ts +2 -1
- package/dist/helpers/event.js +2 -1
- package/dist/helpers/gift-wraps.d.ts +3 -3
- package/dist/helpers/gift-wraps.js +3 -3
- package/dist/helpers/legacy-messages.d.ts +6 -4
- package/dist/helpers/legacy-messages.js +12 -10
- package/dist/helpers/wrapped-messages.d.ts +5 -3
- package/dist/helpers/wrapped-messages.js +5 -3
- package/dist/models/common.d.ts +4 -4
- package/dist/models/common.js +79 -40
- package/dist/models/legacy-messages.d.ts +2 -2
- package/dist/models/legacy-messages.js +11 -10
- package/dist/models/profile.d.ts +1 -1
- package/dist/observable/claim-events.d.ts +2 -2
- package/dist/observable/claim-latest.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
3
|
+
import { Observable, Subject } from "rxjs";
|
|
4
|
+
import { AddressPointerWithoutD } from "../helpers/pointers.js";
|
|
5
|
+
import { IAsyncEventDatabase, IAsyncEventStore } from "./interface.js";
|
|
6
|
+
declare const AsyncEventStore_base: {
|
|
7
|
+
new (...args: any[]): {
|
|
8
|
+
[x: string]: any;
|
|
9
|
+
models: Map<import("./interface.js").ModelConstructor<any, any[], import("./interface.js").IEventStore | IAsyncEventStore>, Map<string, Observable<any>>>;
|
|
10
|
+
modelKeepWarm: number;
|
|
11
|
+
model<T extends unknown, Args extends Array<any>>(constructor: import("./interface.js").ModelConstructor<T, Args, import("./interface.js").IEventStore | IAsyncEventStore>, ...args: Args): Observable<T>;
|
|
12
|
+
filters(filters: Filter | Filter[], onlyNew?: boolean): Observable<NostrEvent>;
|
|
13
|
+
event(pointer: string | EventPointer): Observable<NostrEvent | undefined>;
|
|
14
|
+
replaceable(pointer: AddressPointer | AddressPointerWithoutD): Observable<NostrEvent | undefined>;
|
|
15
|
+
replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
|
|
16
|
+
addressable(pointer: AddressPointer): Observable<NostrEvent | undefined>;
|
|
17
|
+
timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
|
|
18
|
+
profile(user: string | import("nostr-tools/nip19").ProfilePointer): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
|
|
19
|
+
contacts(user: string | import("nostr-tools/nip19").ProfilePointer): Observable<import("nostr-tools/nip19").ProfilePointer[]>;
|
|
20
|
+
mutes(user: string | import("nostr-tools/nip19").ProfilePointer): Observable<import("../helpers/mutes.js").Mutes | undefined>;
|
|
21
|
+
mailboxes(user: string | import("nostr-tools/nip19").ProfilePointer): Observable<{
|
|
22
|
+
inboxes: string[];
|
|
23
|
+
outboxes: string[];
|
|
24
|
+
} | undefined>;
|
|
25
|
+
blossomServers(user: string | import("nostr-tools/nip19").ProfilePointer): Observable<URL[]>;
|
|
26
|
+
reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
|
|
27
|
+
thread(root: string | EventPointer | AddressPointer): Observable<import("../models/thread.js").Thread>;
|
|
28
|
+
comments(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
|
|
29
|
+
events(ids: string[]): Observable<Record<string, NostrEvent | undefined>>;
|
|
30
|
+
replaceableSet(pointers: {
|
|
31
|
+
kind: number;
|
|
32
|
+
pubkey: string;
|
|
33
|
+
identifier?: string;
|
|
34
|
+
}[]): Observable<Record<string, NostrEvent | undefined>>;
|
|
35
|
+
};
|
|
36
|
+
} & {
|
|
37
|
+
new (): {};
|
|
38
|
+
};
|
|
39
|
+
/** An async wrapper around an async event database that handles replaceable events, deletes, and models */
|
|
40
|
+
export declare class AsyncEventStore extends AsyncEventStore_base implements IAsyncEventStore {
|
|
41
|
+
database: IAsyncEventDatabase;
|
|
42
|
+
/** Enable this to keep old versions of replaceable events */
|
|
43
|
+
keepOldVersions: boolean;
|
|
44
|
+
/** Enable this to keep expired events */
|
|
45
|
+
keepExpired: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* A method used to verify new events before added them
|
|
48
|
+
* @returns true if the event is valid, false if it should be ignored
|
|
49
|
+
*/
|
|
50
|
+
verifyEvent?: (event: NostrEvent) => boolean;
|
|
51
|
+
/** A stream of new events added to the store */
|
|
52
|
+
insert$: Subject<import("nostr-tools").Event>;
|
|
53
|
+
/** A stream of events that have been updated */
|
|
54
|
+
update$: Subject<import("nostr-tools").Event>;
|
|
55
|
+
/** A stream of events that have been removed */
|
|
56
|
+
remove$: Subject<import("nostr-tools").Event>;
|
|
57
|
+
/**
|
|
58
|
+
* A method that will be called when an event isn't found in the store
|
|
59
|
+
* @experimental
|
|
60
|
+
*/
|
|
61
|
+
eventLoader?: (pointer: EventPointer) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
|
|
62
|
+
/**
|
|
63
|
+
* A method that will be called when a replaceable event isn't found in the store
|
|
64
|
+
* @experimental
|
|
65
|
+
*/
|
|
66
|
+
replaceableLoader?: (pointer: AddressPointerWithoutD) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
|
|
67
|
+
/**
|
|
68
|
+
* A method that will be called when an addressable event isn't found in the store
|
|
69
|
+
* @experimental
|
|
70
|
+
*/
|
|
71
|
+
addressableLoader?: (pointer: AddressPointer) => Observable<NostrEvent> | Promise<NostrEvent | undefined>;
|
|
72
|
+
constructor(database: IAsyncEventDatabase);
|
|
73
|
+
protected deletedIds: Set<string>;
|
|
74
|
+
protected deletedCoords: Map<string, number>;
|
|
75
|
+
protected checkDeleted(event: string | NostrEvent): boolean;
|
|
76
|
+
protected expirations: Map<string, number>;
|
|
77
|
+
/** Adds an event to the expiration map */
|
|
78
|
+
protected addExpiration(event: NostrEvent): void;
|
|
79
|
+
protected expirationTimeout: number | null;
|
|
80
|
+
protected nextExpirationCheck: number | null;
|
|
81
|
+
protected handleExpiringEvent(event: NostrEvent): void;
|
|
82
|
+
/** Remove expired events from the store */
|
|
83
|
+
protected pruneExpired(): Promise<void>;
|
|
84
|
+
protected handleDeleteEvent(deleteEvent: NostrEvent): Promise<void>;
|
|
85
|
+
/** Copies important metadata from and identical event to another */
|
|
86
|
+
static mergeDuplicateEvent(source: NostrEvent, dest: NostrEvent): void;
|
|
87
|
+
/**
|
|
88
|
+
* Adds an event to the store and update subscriptions
|
|
89
|
+
* @returns The existing event or the event that was added, if it was ignored returns null
|
|
90
|
+
*/
|
|
91
|
+
add(event: NostrEvent, fromRelay?: string): Promise<NostrEvent | null>;
|
|
92
|
+
/** Removes an event from the store and updates subscriptions */
|
|
93
|
+
remove(event: string | NostrEvent): Promise<boolean>;
|
|
94
|
+
/** Add an event to the store and notifies all subscribes it has updated */
|
|
95
|
+
update(event: NostrEvent): Promise<void>;
|
|
96
|
+
/** Passthrough method for the database.touch */
|
|
97
|
+
touch(event: NostrEvent): Promise<void>;
|
|
98
|
+
/** Pass through method for the database.unclaimed */
|
|
99
|
+
unclaimed(): AsyncGenerator<NostrEvent>;
|
|
100
|
+
/** Removes any event that is not being used by a subscription */
|
|
101
|
+
prune(limit?: number): Promise<number>;
|
|
102
|
+
/** Check if the store has an event by id */
|
|
103
|
+
hasEvent(id: string): Promise<boolean>;
|
|
104
|
+
/** Get an event by id from the store */
|
|
105
|
+
getEvent(id: string): Promise<NostrEvent | undefined>;
|
|
106
|
+
/** Check if the store has a replaceable event */
|
|
107
|
+
hasReplaceable(kind: number, pubkey: string, d?: string): Promise<boolean>;
|
|
108
|
+
/** Gets the latest version of a replaceable event */
|
|
109
|
+
getReplaceable(kind: number, pubkey: string, identifier?: string): Promise<NostrEvent | undefined>;
|
|
110
|
+
/** Returns all versions of a replaceable event */
|
|
111
|
+
getReplaceableHistory(kind: number, pubkey: string, identifier?: string): Promise<NostrEvent[] | undefined>;
|
|
112
|
+
/** Get all events matching a filter */
|
|
113
|
+
getByFilters(filters: Filter | Filter[]): Promise<Set<NostrEvent>>;
|
|
114
|
+
/** Returns a timeline of events that match filters */
|
|
115
|
+
getTimeline(filters: Filter | Filter[]): Promise<NostrEvent[]>;
|
|
116
|
+
/** Sets the claim on the event and touches it */
|
|
117
|
+
claim(event: NostrEvent, claim: any): Promise<void>;
|
|
118
|
+
/** Checks if an event is claimed by anything */
|
|
119
|
+
isClaimed(event: NostrEvent): Promise<boolean>;
|
|
120
|
+
/** Removes a claim from an event */
|
|
121
|
+
removeClaim(event: NostrEvent, claim: any): Promise<void>;
|
|
122
|
+
/** Removes all claims on an event */
|
|
123
|
+
clearClaim(event: NostrEvent): Promise<void>;
|
|
124
|
+
/** Returns an observable that completes when an event is removed */
|
|
125
|
+
removed(id: string): Observable<never>;
|
|
126
|
+
/** Creates an observable that emits when event is updated */
|
|
127
|
+
updated(event: string | NostrEvent): Observable<NostrEvent>;
|
|
128
|
+
}
|
|
129
|
+
export {};
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { isAddressableKind } from "nostr-tools/kinds";
|
|
3
|
+
import { EMPTY, filter, mergeMap, Subject, take } from "rxjs";
|
|
4
|
+
import { getDeleteCoordinates, getDeleteIds } from "../helpers/delete.js";
|
|
5
|
+
import { createReplaceableAddress, EventStoreSymbol, FromCacheSymbol, isReplaceable } from "../helpers/event.js";
|
|
6
|
+
import { getExpirationTimestamp } from "../helpers/expiration.js";
|
|
7
|
+
import { parseCoordinate } from "../helpers/pointers.js";
|
|
8
|
+
import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
|
|
9
|
+
import { unixNow } from "../helpers/time.js";
|
|
10
|
+
// Import common models
|
|
11
|
+
import { EventStoreModelMixin } from "./model-mixin.js";
|
|
12
|
+
/** An async wrapper around an async event database that handles replaceable events, deletes, and models */
|
|
13
|
+
export class AsyncEventStore extends EventStoreModelMixin(class {
|
|
14
|
+
}) {
|
|
15
|
+
database;
|
|
16
|
+
/** Enable this to keep old versions of replaceable events */
|
|
17
|
+
keepOldVersions = false;
|
|
18
|
+
/** Enable this to keep expired events */
|
|
19
|
+
keepExpired = false;
|
|
20
|
+
/**
|
|
21
|
+
* A method used to verify new events before added them
|
|
22
|
+
* @returns true if the event is valid, false if it should be ignored
|
|
23
|
+
*/
|
|
24
|
+
verifyEvent;
|
|
25
|
+
/** A stream of new events added to the store */
|
|
26
|
+
insert$ = new Subject();
|
|
27
|
+
/** A stream of events that have been updated */
|
|
28
|
+
update$ = new Subject();
|
|
29
|
+
/** A stream of events that have been removed */
|
|
30
|
+
remove$ = new Subject();
|
|
31
|
+
/**
|
|
32
|
+
* A method that will be called when an event isn't found in the store
|
|
33
|
+
* @experimental
|
|
34
|
+
*/
|
|
35
|
+
eventLoader;
|
|
36
|
+
/**
|
|
37
|
+
* A method that will be called when a replaceable event isn't found in the store
|
|
38
|
+
* @experimental
|
|
39
|
+
*/
|
|
40
|
+
replaceableLoader;
|
|
41
|
+
/**
|
|
42
|
+
* A method that will be called when an addressable event isn't found in the store
|
|
43
|
+
* @experimental
|
|
44
|
+
*/
|
|
45
|
+
addressableLoader;
|
|
46
|
+
constructor(database) {
|
|
47
|
+
super();
|
|
48
|
+
this.database = database;
|
|
49
|
+
// when events are added to the database, add the symbol
|
|
50
|
+
this.insert$.subscribe((event) => {
|
|
51
|
+
Reflect.set(event, EventStoreSymbol, this);
|
|
52
|
+
});
|
|
53
|
+
// when events are removed from the database, remove the symbol
|
|
54
|
+
this.remove$.subscribe((event) => {
|
|
55
|
+
Reflect.deleteProperty(event, EventStoreSymbol);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// delete state
|
|
59
|
+
deletedIds = new Set();
|
|
60
|
+
deletedCoords = new Map();
|
|
61
|
+
checkDeleted(event) {
|
|
62
|
+
if (typeof event === "string")
|
|
63
|
+
return this.deletedIds.has(event);
|
|
64
|
+
else {
|
|
65
|
+
if (this.deletedIds.has(event.id))
|
|
66
|
+
return true;
|
|
67
|
+
if (isAddressableKind(event.kind)) {
|
|
68
|
+
const identifier = event.tags.find((t) => t[0] === "d")?.[1];
|
|
69
|
+
const deleted = this.deletedCoords.get(createReplaceableAddress(event.kind, event.pubkey, identifier));
|
|
70
|
+
if (deleted)
|
|
71
|
+
return deleted > event.created_at;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
expirations = new Map();
|
|
77
|
+
/** Adds an event to the expiration map */
|
|
78
|
+
addExpiration(event) {
|
|
79
|
+
const expiration = getExpirationTimestamp(event);
|
|
80
|
+
if (expiration && Number.isFinite(expiration))
|
|
81
|
+
this.expirations.set(event.id, expiration);
|
|
82
|
+
}
|
|
83
|
+
expirationTimeout = null;
|
|
84
|
+
nextExpirationCheck = null;
|
|
85
|
+
handleExpiringEvent(event) {
|
|
86
|
+
const expiration = getExpirationTimestamp(event);
|
|
87
|
+
if (!expiration)
|
|
88
|
+
return;
|
|
89
|
+
// Add event to expiration map
|
|
90
|
+
this.expirations.set(event.id, expiration);
|
|
91
|
+
// Exit if the next check is already less than the next expiration
|
|
92
|
+
if (this.expirationTimeout && this.nextExpirationCheck && this.nextExpirationCheck < expiration)
|
|
93
|
+
return;
|
|
94
|
+
// Set timeout to prune expired events
|
|
95
|
+
if (this.expirationTimeout)
|
|
96
|
+
clearTimeout(this.expirationTimeout);
|
|
97
|
+
const timeout = expiration - unixNow();
|
|
98
|
+
this.expirationTimeout = setTimeout(this.pruneExpired.bind(this), timeout * 1000 + 10);
|
|
99
|
+
this.nextExpirationCheck = expiration;
|
|
100
|
+
}
|
|
101
|
+
/** Remove expired events from the store */
|
|
102
|
+
async pruneExpired() {
|
|
103
|
+
const now = unixNow();
|
|
104
|
+
for (const [id, expiration] of this.expirations) {
|
|
105
|
+
if (expiration <= now) {
|
|
106
|
+
this.expirations.delete(id);
|
|
107
|
+
await this.remove(id);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Cleanup timers
|
|
111
|
+
if (this.expirationTimeout)
|
|
112
|
+
clearTimeout(this.expirationTimeout);
|
|
113
|
+
this.nextExpirationCheck = null;
|
|
114
|
+
this.expirationTimeout = null;
|
|
115
|
+
}
|
|
116
|
+
// handling delete events
|
|
117
|
+
async handleDeleteEvent(deleteEvent) {
|
|
118
|
+
const ids = getDeleteIds(deleteEvent);
|
|
119
|
+
for (const id of ids) {
|
|
120
|
+
this.deletedIds.add(id);
|
|
121
|
+
// remove deleted events in the database
|
|
122
|
+
await this.remove(id);
|
|
123
|
+
}
|
|
124
|
+
const coords = getDeleteCoordinates(deleteEvent);
|
|
125
|
+
for (const coord of coords) {
|
|
126
|
+
this.deletedCoords.set(coord, Math.max(this.deletedCoords.get(coord) ?? 0, deleteEvent.created_at));
|
|
127
|
+
// Parse the nostr address coordinate
|
|
128
|
+
const parsed = parseCoordinate(coord);
|
|
129
|
+
if (!parsed)
|
|
130
|
+
continue;
|
|
131
|
+
// Remove older versions of replaceable events
|
|
132
|
+
const events = await this.database.getReplaceableHistory(parsed.kind, parsed.pubkey, parsed.identifier);
|
|
133
|
+
if (events) {
|
|
134
|
+
for (const event of events) {
|
|
135
|
+
if (event.created_at < deleteEvent.created_at)
|
|
136
|
+
await this.remove(event);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Copies important metadata from and identical event to another */
|
|
142
|
+
static mergeDuplicateEvent(source, dest) {
|
|
143
|
+
const relays = getSeenRelays(source);
|
|
144
|
+
if (relays) {
|
|
145
|
+
for (const relay of relays)
|
|
146
|
+
addSeenRelay(dest, relay);
|
|
147
|
+
}
|
|
148
|
+
// copy the from cache symbol only if its true
|
|
149
|
+
const fromCache = Reflect.get(source, FromCacheSymbol);
|
|
150
|
+
if (fromCache && !Reflect.get(dest, FromCacheSymbol))
|
|
151
|
+
Reflect.set(dest, FromCacheSymbol, fromCache);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Adds an event to the store and update subscriptions
|
|
155
|
+
* @returns The existing event or the event that was added, if it was ignored returns null
|
|
156
|
+
*/
|
|
157
|
+
async add(event, fromRelay) {
|
|
158
|
+
// Handle delete events differently
|
|
159
|
+
if (event.kind === kinds.EventDeletion)
|
|
160
|
+
await this.handleDeleteEvent(event);
|
|
161
|
+
// Ignore if the event was deleted
|
|
162
|
+
if (this.checkDeleted(event))
|
|
163
|
+
return event;
|
|
164
|
+
// Reject expired events if keepExpired is false
|
|
165
|
+
const expiration = getExpirationTimestamp(event);
|
|
166
|
+
if (this.keepExpired === false && expiration && expiration <= unixNow())
|
|
167
|
+
return null;
|
|
168
|
+
// Get the replaceable identifier
|
|
169
|
+
const identifier = isReplaceable(event.kind) ? event.tags.find((t) => t[0] === "d")?.[1] : undefined;
|
|
170
|
+
// Don't insert the event if there is already a newer version
|
|
171
|
+
if (!this.keepOldVersions && isReplaceable(event.kind)) {
|
|
172
|
+
const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
|
|
173
|
+
// If there is already a newer version, copy cached symbols and return existing event
|
|
174
|
+
if (existing && existing.length > 0 && existing[0].created_at >= event.created_at) {
|
|
175
|
+
AsyncEventStore.mergeDuplicateEvent(event, existing[0]);
|
|
176
|
+
return existing[0];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else if (await this.database.hasEvent(event.id)) {
|
|
180
|
+
// Duplicate event, copy symbols and return existing event
|
|
181
|
+
const existing = await this.database.getEvent(event.id);
|
|
182
|
+
if (existing) {
|
|
183
|
+
AsyncEventStore.mergeDuplicateEvent(event, existing);
|
|
184
|
+
return existing;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Verify event before inserting into the database
|
|
188
|
+
if (this.verifyEvent && this.verifyEvent(event) === false)
|
|
189
|
+
return null;
|
|
190
|
+
// Insert event into database
|
|
191
|
+
const inserted = await this.database.add(event);
|
|
192
|
+
// If the event was ignored, return null
|
|
193
|
+
if (inserted === null)
|
|
194
|
+
return null;
|
|
195
|
+
// Copy cached data if its a duplicate event
|
|
196
|
+
if (event !== inserted)
|
|
197
|
+
AsyncEventStore.mergeDuplicateEvent(event, inserted);
|
|
198
|
+
// attach relay this event was from
|
|
199
|
+
if (fromRelay)
|
|
200
|
+
addSeenRelay(inserted, fromRelay);
|
|
201
|
+
// Emit insert$ signal
|
|
202
|
+
if (inserted === event)
|
|
203
|
+
this.insert$.next(inserted);
|
|
204
|
+
// remove all old version of the replaceable event
|
|
205
|
+
if (!this.keepOldVersions && isReplaceable(event.kind)) {
|
|
206
|
+
const existing = await this.database.getReplaceableHistory(event.kind, event.pubkey, identifier);
|
|
207
|
+
if (existing) {
|
|
208
|
+
const older = Array.from(existing).filter((e) => e.created_at < event.created_at);
|
|
209
|
+
for (const old of older)
|
|
210
|
+
await this.remove(old);
|
|
211
|
+
// return the newest version of the replaceable event
|
|
212
|
+
// most of the time this will be === event, but not always
|
|
213
|
+
if (existing.length !== older.length)
|
|
214
|
+
return existing[0];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Add event to expiration map
|
|
218
|
+
if (this.keepExpired === false && expiration)
|
|
219
|
+
this.handleExpiringEvent(inserted);
|
|
220
|
+
return inserted;
|
|
221
|
+
}
|
|
222
|
+
/** Removes an event from the store and updates subscriptions */
|
|
223
|
+
async remove(event) {
|
|
224
|
+
// Get the current instance from the database
|
|
225
|
+
const e = await this.database.getEvent(typeof event === "string" ? event : event.id);
|
|
226
|
+
if (!e)
|
|
227
|
+
return false;
|
|
228
|
+
const removed = await this.database.remove(event);
|
|
229
|
+
if (removed && e)
|
|
230
|
+
this.remove$.next(e);
|
|
231
|
+
return removed;
|
|
232
|
+
}
|
|
233
|
+
/** Add an event to the store and notifies all subscribes it has updated */
|
|
234
|
+
async update(event) {
|
|
235
|
+
// Map the event to the current instance in the database
|
|
236
|
+
const e = await this.database.add(event);
|
|
237
|
+
if (!e)
|
|
238
|
+
return;
|
|
239
|
+
await this.database.update(event);
|
|
240
|
+
this.update$.next(event);
|
|
241
|
+
}
|
|
242
|
+
/** Passthrough method for the database.touch */
|
|
243
|
+
async touch(event) {
|
|
244
|
+
return await this.database.touch(event);
|
|
245
|
+
}
|
|
246
|
+
/** Pass through method for the database.unclaimed */
|
|
247
|
+
unclaimed() {
|
|
248
|
+
return this.database.unclaimed();
|
|
249
|
+
}
|
|
250
|
+
/** Removes any event that is not being used by a subscription */
|
|
251
|
+
async prune(limit) {
|
|
252
|
+
let removed = 0;
|
|
253
|
+
const generator = this.database.unclaimed();
|
|
254
|
+
for await (const event of generator) {
|
|
255
|
+
await this.remove(event);
|
|
256
|
+
removed++;
|
|
257
|
+
if (limit && removed >= limit)
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
return removed;
|
|
261
|
+
}
|
|
262
|
+
/** Check if the store has an event by id */
|
|
263
|
+
async hasEvent(id) {
|
|
264
|
+
return await this.database.hasEvent(id);
|
|
265
|
+
}
|
|
266
|
+
/** Get an event by id from the store */
|
|
267
|
+
async getEvent(id) {
|
|
268
|
+
return await this.database.getEvent(id);
|
|
269
|
+
}
|
|
270
|
+
/** Check if the store has a replaceable event */
|
|
271
|
+
async hasReplaceable(kind, pubkey, d) {
|
|
272
|
+
return await this.database.hasReplaceable(kind, pubkey, d);
|
|
273
|
+
}
|
|
274
|
+
/** Gets the latest version of a replaceable event */
|
|
275
|
+
async getReplaceable(kind, pubkey, identifier) {
|
|
276
|
+
return await this.database.getReplaceable(kind, pubkey, identifier);
|
|
277
|
+
}
|
|
278
|
+
/** Returns all versions of a replaceable event */
|
|
279
|
+
async getReplaceableHistory(kind, pubkey, identifier) {
|
|
280
|
+
return await this.database.getReplaceableHistory(kind, pubkey, identifier);
|
|
281
|
+
}
|
|
282
|
+
/** Get all events matching a filter */
|
|
283
|
+
async getByFilters(filters) {
|
|
284
|
+
return await this.database.getByFilters(filters);
|
|
285
|
+
}
|
|
286
|
+
/** Returns a timeline of events that match filters */
|
|
287
|
+
async getTimeline(filters) {
|
|
288
|
+
return await this.database.getTimeline(filters);
|
|
289
|
+
}
|
|
290
|
+
/** Sets the claim on the event and touches it */
|
|
291
|
+
async claim(event, claim) {
|
|
292
|
+
return await this.database.claim(event, claim);
|
|
293
|
+
}
|
|
294
|
+
/** Checks if an event is claimed by anything */
|
|
295
|
+
async isClaimed(event) {
|
|
296
|
+
return await this.database.isClaimed(event);
|
|
297
|
+
}
|
|
298
|
+
/** Removes a claim from an event */
|
|
299
|
+
async removeClaim(event, claim) {
|
|
300
|
+
return await this.database.removeClaim(event, claim);
|
|
301
|
+
}
|
|
302
|
+
/** Removes all claims on an event */
|
|
303
|
+
async clearClaim(event) {
|
|
304
|
+
return await this.database.clearClaim(event);
|
|
305
|
+
}
|
|
306
|
+
/** Returns an observable that completes when an event is removed */
|
|
307
|
+
removed(id) {
|
|
308
|
+
const deleted = this.checkDeleted(id);
|
|
309
|
+
if (deleted)
|
|
310
|
+
return EMPTY;
|
|
311
|
+
return this.remove$.pipe(
|
|
312
|
+
// listen for removed events
|
|
313
|
+
filter((e) => e.id === id),
|
|
314
|
+
// complete as soon as we find a matching removed event
|
|
315
|
+
take(1),
|
|
316
|
+
// switch to empty
|
|
317
|
+
mergeMap(() => EMPTY));
|
|
318
|
+
}
|
|
319
|
+
/** Creates an observable that emits when event is updated */
|
|
320
|
+
updated(event) {
|
|
321
|
+
return this.update$.pipe(filter((e) => e.id === event || e === event));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { LRU } from "../helpers/lru.js";
|
|
3
|
+
import { IEventDatabase } from "./interface.js";
|
|
4
|
+
/** An in-memory database of events */
|
|
5
|
+
export declare class InMemoryEventDatabase implements IEventDatabase {
|
|
6
|
+
protected log: import("debug").Debugger;
|
|
7
|
+
/** Indexes */
|
|
8
|
+
protected kinds: Map<number, Set<import("nostr-tools").Event>>;
|
|
9
|
+
protected authors: Map<string, Set<import("nostr-tools").Event>>;
|
|
10
|
+
protected tags: LRU<Set<import("nostr-tools").Event>>;
|
|
11
|
+
protected created_at: NostrEvent[];
|
|
12
|
+
/** LRU cache of last events touched */
|
|
13
|
+
events: LRU<import("nostr-tools").Event>;
|
|
14
|
+
/** A sorted array of replaceable events by address */
|
|
15
|
+
protected replaceable: Map<string, import("nostr-tools").Event[]>;
|
|
16
|
+
/** The number of events in the event set */
|
|
17
|
+
get size(): number;
|
|
18
|
+
/** Checks if the database contains an event without touching it */
|
|
19
|
+
hasEvent(id: string): boolean;
|
|
20
|
+
/** Gets a single event based on id */
|
|
21
|
+
getEvent(id: string): NostrEvent | undefined;
|
|
22
|
+
/** Checks if the event set has a replaceable event */
|
|
23
|
+
hasReplaceable(kind: number, pubkey: string, identifier?: string): boolean;
|
|
24
|
+
/** Gets the latest replaceable event */
|
|
25
|
+
getReplaceable(kind: number, pubkey: string, identifier?: string): NostrEvent | undefined;
|
|
26
|
+
/** Gets the history of a replaceable event */
|
|
27
|
+
getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[] | undefined;
|
|
28
|
+
/** Gets all events that match the filters */
|
|
29
|
+
getByFilters(filters: Filter | Filter[]): Set<NostrEvent>;
|
|
30
|
+
/** Gets a timeline of events that match the filters */
|
|
31
|
+
getTimeline(filters: Filter | Filter[]): NostrEvent[];
|
|
32
|
+
/** Inserts an event into the database and notifies all subscriptions */
|
|
33
|
+
add(event: NostrEvent): NostrEvent | null;
|
|
34
|
+
/** Removes an event from the database and notifies all subscriptions */
|
|
35
|
+
remove(eventOrId: string | NostrEvent): boolean;
|
|
36
|
+
/** Notify the database that an event has updated */
|
|
37
|
+
update(_event: NostrEvent): void;
|
|
38
|
+
/** A weak map of events that are claimed by other things */
|
|
39
|
+
protected claims: WeakMap<import("nostr-tools").Event, any>;
|
|
40
|
+
/** Moves an event to the top of the LRU cache */
|
|
41
|
+
touch(event: NostrEvent): void;
|
|
42
|
+
/** Sets the claim on the event and touches it */
|
|
43
|
+
claim(event: NostrEvent, claim: any): void;
|
|
44
|
+
/** Checks if an event is claimed by anything */
|
|
45
|
+
isClaimed(event: NostrEvent): boolean;
|
|
46
|
+
/** Removes a claim from an event */
|
|
47
|
+
removeClaim(event: NostrEvent, claim: any): void;
|
|
48
|
+
/** Removes all claims on an event */
|
|
49
|
+
clearClaim(event: NostrEvent): void;
|
|
50
|
+
/** Returns a generator of unclaimed events in order of least used */
|
|
51
|
+
unclaimed(): Generator<NostrEvent>;
|
|
52
|
+
/** Index helper methods */
|
|
53
|
+
protected getKindIndex(kind: number): Set<import("nostr-tools").Event>;
|
|
54
|
+
protected getAuthorsIndex(author: string): Set<import("nostr-tools").Event>;
|
|
55
|
+
protected getTagIndex(tagAndValue: string): Set<import("nostr-tools").Event>;
|
|
56
|
+
/** Iterates over all events by author */
|
|
57
|
+
iterateAuthors(authors: Iterable<string>): Generator<NostrEvent>;
|
|
58
|
+
/** Iterates over all events by indexable tag and value */
|
|
59
|
+
iterateTag(tag: string, values: Iterable<string>): Generator<NostrEvent>;
|
|
60
|
+
/** Iterates over all events by kind */
|
|
61
|
+
iterateKinds(kinds: Iterable<number>): Generator<NostrEvent>;
|
|
62
|
+
/** Iterates over all events by time */
|
|
63
|
+
iterateTime(since: number | undefined, until: number | undefined): Generator<NostrEvent>;
|
|
64
|
+
/** Iterates over all events by id */
|
|
65
|
+
iterateIds(ids: Iterable<string>): Generator<NostrEvent>;
|
|
66
|
+
/** Returns all events that match the filter */
|
|
67
|
+
getEventsForFilter(filter: Filter): Set<NostrEvent>;
|
|
68
|
+
/** Returns all events that match the filters */
|
|
69
|
+
getEventsForFilters(filters: Filter[]): Set<NostrEvent>;
|
|
70
|
+
/** Resets the event set */
|
|
71
|
+
reset(): void;
|
|
72
|
+
}
|
|
73
|
+
/** @deprecated use {@link InMemoryEventDatabase} instead */
|
|
74
|
+
export declare const EventSet: typeof InMemoryEventDatabase;
|