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