applesauce-core 1.2.0 → 2.1.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/README.md +7 -13
- package/dist/event-store/{database.d.ts → event-set.d.ts} +36 -21
- package/dist/event-store/{database.js → event-set.js} +98 -67
- package/dist/event-store/event-store.d.ts +64 -25
- package/dist/event-store/event-store.js +164 -207
- package/dist/event-store/index.d.ts +1 -1
- package/dist/event-store/index.js +1 -1
- package/dist/event-store/interface.d.ts +71 -13
- package/dist/helpers/app-handlers.d.ts +23 -0
- package/dist/helpers/app-handlers.js +68 -0
- package/dist/helpers/article.d.ts +9 -0
- package/dist/helpers/article.js +21 -0
- package/dist/helpers/bolt11.d.ts +1 -0
- package/dist/helpers/bolt11.js +2 -0
- package/dist/helpers/bookmarks.js +1 -2
- package/dist/helpers/emoji.d.ts +10 -2
- package/dist/helpers/emoji.js +21 -3
- package/dist/helpers/encrypted-content-cache.d.ts +15 -0
- package/dist/helpers/encrypted-content-cache.js +125 -0
- package/dist/helpers/encrypted-content.d.ts +48 -0
- package/dist/helpers/encrypted-content.js +65 -0
- package/dist/helpers/encryption.d.ts +5 -0
- package/dist/helpers/encryption.js +10 -0
- package/dist/helpers/event.d.ts +5 -8
- package/dist/helpers/event.js +25 -11
- package/dist/helpers/expiration.d.ts +6 -0
- package/dist/helpers/expiration.js +16 -0
- package/dist/helpers/filter.d.ts +1 -3
- package/dist/helpers/filter.js +1 -3
- package/dist/helpers/gift-wraps.d.ts +17 -5
- package/dist/helpers/gift-wraps.js +65 -27
- package/dist/helpers/groups.d.ts +5 -0
- package/dist/helpers/groups.js +12 -2
- package/dist/helpers/hidden-content.d.ts +27 -32
- package/dist/helpers/hidden-content.js +35 -65
- package/dist/helpers/hidden-tags.d.ts +23 -4
- package/dist/helpers/hidden-tags.js +39 -4
- package/dist/helpers/index.d.ts +11 -1
- package/dist/helpers/index.js +11 -1
- package/dist/helpers/legacy-messages.d.ts +21 -0
- package/dist/helpers/legacy-messages.js +39 -0
- package/dist/helpers/lists.d.ts +3 -1
- package/dist/helpers/lists.js +9 -3
- package/dist/helpers/messages.d.ts +11 -0
- package/dist/helpers/messages.js +19 -0
- package/dist/helpers/mutes.js +1 -1
- package/dist/helpers/pointers.d.ts +33 -9
- package/dist/helpers/pointers.js +80 -44
- package/dist/helpers/profile.d.ts +10 -2
- package/dist/helpers/profile.js +33 -4
- package/dist/helpers/reactions.d.ts +8 -0
- package/dist/helpers/reactions.js +56 -0
- package/dist/helpers/reports.d.ts +28 -0
- package/dist/helpers/reports.js +38 -0
- package/dist/helpers/share.d.ts +10 -1
- package/dist/helpers/share.js +22 -8
- package/dist/helpers/url.d.ts +4 -0
- package/dist/helpers/url.js +20 -0
- package/dist/helpers/user-status.js +2 -1
- package/dist/helpers/wrapped-messages.d.ts +23 -0
- package/dist/helpers/wrapped-messages.js +38 -0
- package/dist/helpers/zap.d.ts +8 -5
- package/dist/helpers/zap.js +11 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/models/blossom.d.ts +3 -0
- package/dist/models/blossom.js +8 -0
- package/dist/models/bookmarks.d.ts +8 -0
- package/dist/{queries → models}/bookmarks.js +9 -9
- package/dist/models/channels.d.ts +11 -0
- package/dist/{queries → models}/channels.js +9 -9
- package/dist/models/comments.d.ts +4 -0
- package/dist/models/comments.js +11 -0
- package/dist/models/common.d.ts +16 -0
- package/dist/models/common.js +176 -0
- package/dist/models/contacts.d.ts +8 -0
- package/dist/{queries → models}/contacts.js +10 -10
- package/dist/models/encrypted-content.d.ts +4 -0
- package/dist/models/encrypted-content.js +11 -0
- package/dist/models/gift-wrap.d.ts +7 -0
- package/dist/models/gift-wrap.js +20 -0
- package/dist/{queries → models}/index.d.ts +6 -2
- package/dist/{queries → models}/index.js +6 -2
- package/dist/models/legacy-messages.d.ts +8 -0
- package/dist/models/legacy-messages.js +29 -0
- package/dist/models/mailboxes.d.ts +6 -0
- package/dist/{queries → models}/mailboxes.js +2 -2
- package/dist/models/mutes.d.ts +8 -0
- package/dist/{queries → models}/mutes.js +9 -9
- package/dist/models/pins.d.ts +4 -0
- package/dist/{queries → models}/pins.js +3 -3
- package/dist/models/profile.d.ts +4 -0
- package/dist/models/profile.js +14 -0
- package/dist/models/reactions.d.ts +4 -0
- package/dist/{queries → models}/reactions.js +2 -2
- package/dist/models/relays.d.ts +27 -0
- package/dist/{queries → models}/relays.js +13 -13
- package/dist/{queries → models}/thread.d.ts +6 -5
- package/dist/{queries → models}/thread.js +4 -3
- package/dist/models/user-status.d.ts +11 -0
- package/dist/{queries → models}/user-status.js +5 -5
- package/dist/models/wrapped-messages.d.ts +25 -0
- package/dist/models/wrapped-messages.js +61 -0
- package/dist/models/zaps.d.ts +9 -0
- package/dist/{queries → models}/zaps.js +11 -3
- package/dist/observable/claim-events.d.ts +3 -3
- package/dist/observable/claim-events.js +4 -4
- package/dist/observable/claim-latest.d.ts +3 -3
- package/dist/observable/claim-latest.js +4 -4
- package/dist/observable/index.d.ts +3 -1
- package/dist/observable/index.js +3 -1
- package/dist/observable/map-events-timeline.d.ts +7 -0
- package/dist/observable/map-events-timeline.js +9 -0
- package/dist/observable/map-events-to-store.d.ts +5 -0
- package/dist/observable/map-events-to-store.js +12 -0
- package/dist/observable/simple-timeout.d.ts +1 -0
- package/dist/observable/simple-timeout.js +1 -0
- package/dist/observable/watch-event-updates.d.ts +7 -0
- package/dist/observable/watch-event-updates.js +25 -0
- package/package.json +11 -16
- package/dist/__tests__/exports.test.d.ts +0 -1
- package/dist/__tests__/exports.test.js +0 -17
- package/dist/__tests__/fixtures.d.ts +0 -8
- package/dist/__tests__/fixtures.js +0 -20
- package/dist/event-store/__tests__/event-store.test.d.ts +0 -1
- package/dist/event-store/__tests__/event-store.test.js +0 -354
- package/dist/helpers/__tests__/blossom.test.d.ts +0 -1
- package/dist/helpers/__tests__/blossom.test.js +0 -13
- package/dist/helpers/__tests__/bookmarks.test.d.ts +0 -1
- package/dist/helpers/__tests__/bookmarks.test.js +0 -88
- package/dist/helpers/__tests__/comment.test.d.ts +0 -1
- package/dist/helpers/__tests__/comment.test.js +0 -249
- package/dist/helpers/__tests__/contacts.test.d.ts +0 -1
- package/dist/helpers/__tests__/contacts.test.js +0 -34
- package/dist/helpers/__tests__/emoji.test.d.ts +0 -1
- package/dist/helpers/__tests__/emoji.test.js +0 -15
- package/dist/helpers/__tests__/event.test.d.ts +0 -1
- package/dist/helpers/__tests__/event.test.js +0 -36
- package/dist/helpers/__tests__/events.test.d.ts +0 -1
- package/dist/helpers/__tests__/events.test.js +0 -32
- package/dist/helpers/__tests__/exports.test.d.ts +0 -1
- package/dist/helpers/__tests__/exports.test.js +0 -220
- package/dist/helpers/__tests__/file-metadata.test.d.ts +0 -1
- package/dist/helpers/__tests__/file-metadata.test.js +0 -103
- package/dist/helpers/__tests__/hidden-tags.test.d.ts +0 -1
- package/dist/helpers/__tests__/hidden-tags.test.js +0 -29
- package/dist/helpers/__tests__/mailboxes.test.d.ts +0 -1
- package/dist/helpers/__tests__/mailboxes.test.js +0 -81
- package/dist/helpers/__tests__/mutes.test.d.ts +0 -1
- package/dist/helpers/__tests__/mutes.test.js +0 -55
- package/dist/helpers/__tests__/nip-19.test.d.ts +0 -1
- package/dist/helpers/__tests__/nip-19.test.js +0 -42
- package/dist/helpers/__tests__/relays.test.d.ts +0 -1
- package/dist/helpers/__tests__/relays.test.js +0 -21
- package/dist/helpers/__tests__/tags.test.d.ts +0 -1
- package/dist/helpers/__tests__/tags.test.js +0 -24
- package/dist/helpers/__tests__/threading.test.d.ts +0 -1
- package/dist/helpers/__tests__/threading.test.js +0 -41
- package/dist/helpers/direct-messages.d.ts +0 -4
- package/dist/helpers/direct-messages.js +0 -5
- package/dist/helpers/nip-19.d.ts +0 -18
- package/dist/helpers/nip-19.js +0 -56
- package/dist/observable/__tests__/claim-events.test.d.ts +0 -1
- package/dist/observable/__tests__/claim-events.test.js +0 -23
- package/dist/observable/__tests__/claim-latest.test.d.ts +0 -1
- package/dist/observable/__tests__/claim-latest.test.js +0 -37
- package/dist/observable/__tests__/exports.test.d.ts +0 -1
- package/dist/observable/__tests__/exports.test.js +0 -18
- package/dist/observable/__tests__/listen-latest-updates.test.d.ts +0 -1
- package/dist/observable/__tests__/listen-latest-updates.test.js +0 -55
- package/dist/observable/__tests__/simple-timeout.test.d.ts +0 -1
- package/dist/observable/__tests__/simple-timeout.test.js +0 -34
- package/dist/observable/listen-latest-updates.d.ts +0 -5
- package/dist/observable/listen-latest-updates.js +0 -12
- package/dist/promise/__tests__/exports.test.d.ts +0 -1
- package/dist/promise/__tests__/exports.test.js +0 -11
- package/dist/queries/__tests__/exports.test.d.ts +0 -1
- package/dist/queries/__tests__/exports.test.js +0 -41
- package/dist/queries/blossom.d.ts +0 -2
- package/dist/queries/blossom.js +0 -5
- package/dist/queries/bookmarks.d.ts +0 -8
- package/dist/queries/channels.d.ts +0 -11
- package/dist/queries/comments.d.ts +0 -4
- package/dist/queries/comments.js +0 -11
- package/dist/queries/contacts.d.ts +0 -8
- package/dist/queries/mailboxes.d.ts +0 -6
- package/dist/queries/mutes.d.ts +0 -8
- package/dist/queries/pins.d.ts +0 -4
- package/dist/queries/profile.d.ts +0 -4
- package/dist/queries/profile.js +0 -7
- package/dist/queries/reactions.d.ts +0 -4
- package/dist/queries/relays.d.ts +0 -27
- package/dist/queries/simple.d.ts +0 -16
- package/dist/queries/simple.js +0 -21
- package/dist/queries/user-status.d.ts +0 -11
- package/dist/queries/zaps.d.ts +0 -5
- package/dist/query-store/__tests__/exports.test.d.ts +0 -1
- package/dist/query-store/__tests__/exports.test.js +0 -12
- package/dist/query-store/__tests__/query-store.test.d.ts +0 -1
- package/dist/query-store/__tests__/query-store.test.js +0 -63
- package/dist/query-store/index.d.ts +0 -1
- package/dist/query-store/index.js +0 -1
- package/dist/query-store/query-store.d.ts +0 -54
- package/dist/query-store/query-store.js +0 -102
|
@@ -1,49 +1,59 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
|
-
import { insertEventIntoDescendingList } from "nostr-tools/utils";
|
|
3
2
|
import { isAddressableKind } from "nostr-tools/kinds";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import { FromCacheSymbol, getEventUID, getReplaceableIdentifier, createReplaceableAddress, getTagValue, isReplaceable, } from "../helpers/event.js";
|
|
7
|
-
import { matchFilters } from "../helpers/filter.js";
|
|
8
|
-
import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
|
|
3
|
+
import { EMPTY, filter, finalize, from, merge, mergeMap, ReplaySubject, share, take, timer } from "rxjs";
|
|
4
|
+
import hash_sum from "hash-sum";
|
|
9
5
|
import { getDeleteCoordinates, getDeleteIds } from "../helpers/delete.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
6
|
+
import { FromCacheSymbol, getReplaceableAddress, getTagValue, isReplaceable } from "../helpers/event.js";
|
|
7
|
+
import { matchFilters } from "../helpers/filter.js";
|
|
12
8
|
import { parseCoordinate } from "../helpers/pointers.js";
|
|
9
|
+
import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
|
|
10
|
+
import { EventModel, EventsModel, ReplaceableModel, ReplaceableSetModel, TimelineModel } from "../models/common.js";
|
|
11
|
+
import { EventSet } from "./event-set.js";
|
|
12
|
+
import { ProfileModel } from "../models/profile.js";
|
|
13
|
+
import { ContactsModel } from "../models/contacts.js";
|
|
14
|
+
import { MuteModel } from "../models/mutes.js";
|
|
15
|
+
import { ReactionsModel } from "../models/reactions.js";
|
|
16
|
+
import { MailboxesModel } from "../models/mailboxes.js";
|
|
17
|
+
import { UserBlossomServersModel } from "../models/blossom.js";
|
|
18
|
+
import { CommentsModel, ThreadModel } from "../models/index.js";
|
|
19
|
+
/** A symbol on an event that marks which event store its part of */
|
|
13
20
|
export const EventStoreSymbol = Symbol.for("event-store");
|
|
14
|
-
function sortDesc(a, b) {
|
|
15
|
-
return b.created_at - a.created_at;
|
|
16
|
-
}
|
|
17
21
|
export class EventStore {
|
|
18
22
|
database;
|
|
19
23
|
/** Enable this to keep old versions of replaceable events */
|
|
20
24
|
keepOldVersions = false;
|
|
21
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* A method used to verify new events before added them
|
|
27
|
+
* @returns true if the event is valid, false if it should be ignored
|
|
28
|
+
*/
|
|
22
29
|
verifyEvent;
|
|
23
30
|
/** A stream of new events added to the store */
|
|
24
|
-
|
|
31
|
+
insert$;
|
|
25
32
|
/** A stream of events that have been updated */
|
|
26
|
-
|
|
33
|
+
update$;
|
|
27
34
|
/** A stream of events that have been removed */
|
|
28
|
-
|
|
35
|
+
remove$;
|
|
29
36
|
constructor() {
|
|
30
|
-
this.database = new
|
|
37
|
+
this.database = new EventSet();
|
|
38
|
+
// verify events before they are added to the database
|
|
31
39
|
this.database.onBeforeInsert = (event) => {
|
|
32
|
-
//
|
|
40
|
+
// Ignore events that are invalid
|
|
33
41
|
if (this.verifyEvent && this.verifyEvent(event) === false)
|
|
34
|
-
|
|
42
|
+
return false;
|
|
43
|
+
else
|
|
44
|
+
return true;
|
|
35
45
|
};
|
|
36
46
|
// when events are added to the database, add the symbol
|
|
37
|
-
this.database.
|
|
47
|
+
this.database.insert$.subscribe((event) => {
|
|
38
48
|
Reflect.set(event, EventStoreSymbol, this);
|
|
39
49
|
});
|
|
40
50
|
// when events are removed from the database, remove the symbol
|
|
41
|
-
this.database.
|
|
51
|
+
this.database.remove$.subscribe((event) => {
|
|
42
52
|
Reflect.deleteProperty(event, EventStoreSymbol);
|
|
43
53
|
});
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
54
|
+
this.insert$ = this.database.insert$;
|
|
55
|
+
this.update$ = this.database.update$;
|
|
56
|
+
this.remove$ = this.database.remove$;
|
|
47
57
|
}
|
|
48
58
|
// delete state
|
|
49
59
|
deletedIds = new Set();
|
|
@@ -55,7 +65,7 @@ export class EventStore {
|
|
|
55
65
|
if (this.deletedIds.has(event.id))
|
|
56
66
|
return true;
|
|
57
67
|
if (isAddressableKind(event.kind)) {
|
|
58
|
-
const deleted = this.deletedCoords.get(
|
|
68
|
+
const deleted = this.deletedCoords.get(getReplaceableAddress(event));
|
|
59
69
|
if (deleted)
|
|
60
70
|
return deleted > event.created_at;
|
|
61
71
|
}
|
|
@@ -70,7 +80,7 @@ export class EventStore {
|
|
|
70
80
|
// remove deleted events in the database
|
|
71
81
|
const event = this.database.getEvent(id);
|
|
72
82
|
if (event)
|
|
73
|
-
this.database.
|
|
83
|
+
this.database.remove(event);
|
|
74
84
|
}
|
|
75
85
|
const coords = getDeleteCoordinates(deleteEvent);
|
|
76
86
|
for (const coord of coords) {
|
|
@@ -80,10 +90,10 @@ export class EventStore {
|
|
|
80
90
|
if (!parsed)
|
|
81
91
|
continue;
|
|
82
92
|
// Remove older versions of replaceable events
|
|
83
|
-
const events = this.database.
|
|
93
|
+
const events = this.database.getReplaceableHistory(parsed.kind, parsed.pubkey, parsed.identifier) ?? [];
|
|
84
94
|
for (const event of events) {
|
|
85
95
|
if (event.created_at < deleteEvent.created_at)
|
|
86
|
-
this.database.
|
|
96
|
+
this.database.remove(event);
|
|
87
97
|
}
|
|
88
98
|
}
|
|
89
99
|
}
|
|
@@ -100,8 +110,8 @@ export class EventStore {
|
|
|
100
110
|
Reflect.set(dest, FromCacheSymbol, fromCache);
|
|
101
111
|
}
|
|
102
112
|
/**
|
|
103
|
-
* Adds an event to the
|
|
104
|
-
* @
|
|
113
|
+
* Adds an event to the store and update subscriptions
|
|
114
|
+
* @returns The existing event or the event that was added, if it was ignored returns null
|
|
105
115
|
*/
|
|
106
116
|
add(event, fromRelay) {
|
|
107
117
|
if (event.kind === kinds.EventDeletion)
|
|
@@ -113,7 +123,7 @@ export class EventStore {
|
|
|
113
123
|
const d = isReplaceable(event.kind) ? getTagValue(event, "d") : undefined;
|
|
114
124
|
// Don't insert the event if there is already a newer version
|
|
115
125
|
if (!this.keepOldVersions && isReplaceable(event.kind)) {
|
|
116
|
-
const existing = this.database.
|
|
126
|
+
const existing = this.database.getReplaceableHistory(event.kind, event.pubkey, d);
|
|
117
127
|
// If there is already a newer version, copy cached symbols and return existing event
|
|
118
128
|
if (existing && existing.length > 0 && existing[0].created_at >= event.created_at) {
|
|
119
129
|
EventStore.mergeDuplicateEvent(event, existing[0]);
|
|
@@ -129,7 +139,10 @@ export class EventStore {
|
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
141
|
// Insert event into database
|
|
132
|
-
const inserted = this.database.
|
|
142
|
+
const inserted = this.database.add(event);
|
|
143
|
+
// If the event was ignored, return null
|
|
144
|
+
if (inserted === null)
|
|
145
|
+
return null;
|
|
133
146
|
// Copy cached data if its a duplicate event
|
|
134
147
|
if (event !== inserted)
|
|
135
148
|
EventStore.mergeDuplicateEvent(event, inserted);
|
|
@@ -138,11 +151,11 @@ export class EventStore {
|
|
|
138
151
|
addSeenRelay(inserted, fromRelay);
|
|
139
152
|
// remove all old version of the replaceable event
|
|
140
153
|
if (!this.keepOldVersions && isReplaceable(event.kind)) {
|
|
141
|
-
const existing = this.database.
|
|
154
|
+
const existing = this.database.getReplaceableHistory(event.kind, event.pubkey, d);
|
|
142
155
|
if (existing) {
|
|
143
156
|
const older = Array.from(existing).filter((e) => e.created_at < event.created_at);
|
|
144
157
|
for (const old of older)
|
|
145
|
-
this.database.
|
|
158
|
+
this.database.remove(old);
|
|
146
159
|
// return the newest version of the replaceable event
|
|
147
160
|
// most of the time this will be === event, but not always
|
|
148
161
|
if (existing.length !== older.length)
|
|
@@ -153,24 +166,21 @@ export class EventStore {
|
|
|
153
166
|
}
|
|
154
167
|
/** Removes an event from the database and updates subscriptions */
|
|
155
168
|
remove(event) {
|
|
156
|
-
return this.database.
|
|
157
|
-
}
|
|
158
|
-
/** Removes any event that is not being used by a subscription */
|
|
159
|
-
prune(max) {
|
|
160
|
-
return this.database.prune(max);
|
|
169
|
+
return this.database.remove(event);
|
|
161
170
|
}
|
|
162
171
|
/** Add an event to the store and notifies all subscribes it has updated */
|
|
163
172
|
update(event) {
|
|
164
|
-
return this.database.
|
|
173
|
+
return this.database.update(event);
|
|
165
174
|
}
|
|
166
|
-
/**
|
|
167
|
-
|
|
168
|
-
return this.database.
|
|
175
|
+
/** Removes any event that is not being used by a subscription */
|
|
176
|
+
prune(max) {
|
|
177
|
+
return this.database.prune(max);
|
|
169
178
|
}
|
|
170
|
-
/** Check if the store has an event */
|
|
179
|
+
/** Check if the store has an event by id */
|
|
171
180
|
hasEvent(id) {
|
|
172
181
|
return this.database.hasEvent(id);
|
|
173
182
|
}
|
|
183
|
+
/** Get an event by id from the store */
|
|
174
184
|
getEvent(id) {
|
|
175
185
|
return this.database.getEvent(id);
|
|
176
186
|
}
|
|
@@ -179,19 +189,73 @@ export class EventStore {
|
|
|
179
189
|
return this.database.hasReplaceable(kind, pubkey, d);
|
|
180
190
|
}
|
|
181
191
|
/** Gets the latest version of a replaceable event */
|
|
182
|
-
getReplaceable(kind, pubkey,
|
|
183
|
-
return this.database.getReplaceable(kind, pubkey,
|
|
192
|
+
getReplaceable(kind, pubkey, identifier) {
|
|
193
|
+
return this.database.getReplaceable(kind, pubkey, identifier);
|
|
184
194
|
}
|
|
185
195
|
/** Returns all versions of a replaceable event */
|
|
186
|
-
getReplaceableHistory(kind, pubkey,
|
|
187
|
-
return this.database.
|
|
196
|
+
getReplaceableHistory(kind, pubkey, identifier) {
|
|
197
|
+
return this.database.getReplaceableHistory(kind, pubkey, identifier);
|
|
198
|
+
}
|
|
199
|
+
/** Get all events matching a filter */
|
|
200
|
+
getByFilters(filters) {
|
|
201
|
+
return this.database.getByFilters(filters);
|
|
188
202
|
}
|
|
189
203
|
/** Returns a timeline of events that match filters */
|
|
190
204
|
getTimeline(filters) {
|
|
191
|
-
return
|
|
205
|
+
return this.database.getTimeline(filters);
|
|
206
|
+
}
|
|
207
|
+
/** Sets the claim on the event and touches it */
|
|
208
|
+
claim(event, claim) {
|
|
209
|
+
this.database.claim(event, claim);
|
|
210
|
+
}
|
|
211
|
+
/** Checks if an event is claimed by anything */
|
|
212
|
+
isClaimed(event) {
|
|
213
|
+
return this.database.isClaimed(event);
|
|
214
|
+
}
|
|
215
|
+
/** Removes a claim from an event */
|
|
216
|
+
removeClaim(event, claim) {
|
|
217
|
+
this.database.removeClaim(event, claim);
|
|
218
|
+
}
|
|
219
|
+
/** Removes all claims on an event */
|
|
220
|
+
clearClaim(event) {
|
|
221
|
+
this.database.clearClaim(event);
|
|
222
|
+
}
|
|
223
|
+
/** A directory of all active models */
|
|
224
|
+
models = new Map();
|
|
225
|
+
/** How long a model should be kept "warm" while nothing is subscribed to it */
|
|
226
|
+
modelKeepWarm = 60_000;
|
|
227
|
+
/** Get or create a model on the event store */
|
|
228
|
+
model(constructor, ...args) {
|
|
229
|
+
let models = this.models.get(constructor);
|
|
230
|
+
if (!models) {
|
|
231
|
+
models = new Map();
|
|
232
|
+
this.models.set(constructor, models);
|
|
233
|
+
}
|
|
234
|
+
const key = constructor.getKey ? constructor.getKey(...args) : hash_sum(args);
|
|
235
|
+
let model = models.get(key);
|
|
236
|
+
// Create the model if it does not exist
|
|
237
|
+
if (!model) {
|
|
238
|
+
const cleanup = () => {
|
|
239
|
+
// Remove the model from the cache if its the same one
|
|
240
|
+
if (models.get(key) === model)
|
|
241
|
+
models.delete(key);
|
|
242
|
+
};
|
|
243
|
+
model = constructor(...args)(this).pipe(
|
|
244
|
+
// remove the model when its unsubscribed
|
|
245
|
+
finalize(cleanup),
|
|
246
|
+
// only subscribe to models once for all subscriptions
|
|
247
|
+
share({
|
|
248
|
+
connector: () => new ReplaySubject(1),
|
|
249
|
+
resetOnComplete: () => timer(this.modelKeepWarm),
|
|
250
|
+
resetOnRefCountZero: () => timer(this.modelKeepWarm),
|
|
251
|
+
}));
|
|
252
|
+
// Add the model to the cache
|
|
253
|
+
models.set(key, model);
|
|
254
|
+
}
|
|
255
|
+
return model;
|
|
192
256
|
}
|
|
193
257
|
/**
|
|
194
|
-
* Creates an observable that streams all events that match the filter
|
|
258
|
+
* Creates an observable that streams all events that match the filter
|
|
195
259
|
* @param filters
|
|
196
260
|
* @param [onlyNew=false] Only subscribe to new events
|
|
197
261
|
*/
|
|
@@ -199,16 +263,16 @@ export class EventStore {
|
|
|
199
263
|
filters = Array.isArray(filters) ? filters : [filters];
|
|
200
264
|
return merge(
|
|
201
265
|
// merge existing events
|
|
202
|
-
onlyNew ? EMPTY : from(this.
|
|
266
|
+
onlyNew ? EMPTY : from(this.getByFilters(filters)),
|
|
203
267
|
// subscribe to future events
|
|
204
|
-
this.
|
|
268
|
+
this.insert$.pipe(filter((e) => matchFilters(filters, e))));
|
|
205
269
|
}
|
|
206
270
|
/** Returns an observable that completes when an event is removed */
|
|
207
271
|
removed(id) {
|
|
208
272
|
const deleted = this.checkDeleted(id);
|
|
209
273
|
if (deleted)
|
|
210
274
|
return EMPTY;
|
|
211
|
-
return this.
|
|
275
|
+
return this.remove$.pipe(
|
|
212
276
|
// listen for removed events
|
|
213
277
|
filter((e) => e.id === id),
|
|
214
278
|
// complete as soon as we find a matching removed event
|
|
@@ -218,166 +282,59 @@ export class EventStore {
|
|
|
218
282
|
}
|
|
219
283
|
/** Creates an observable that emits when event is updated */
|
|
220
284
|
updated(event) {
|
|
221
|
-
return this.database.
|
|
285
|
+
return this.database.update$.pipe(filter((e) => e.id === event || e === event));
|
|
222
286
|
}
|
|
223
|
-
|
|
287
|
+
// Helper methods for creating models
|
|
288
|
+
/** Creates a {@link EventModel} */
|
|
224
289
|
event(id) {
|
|
225
|
-
return
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
this.removed(id).pipe(endWith(undefined))).pipe(
|
|
237
|
-
// claim all events
|
|
238
|
-
claimLatest(this.database));
|
|
239
|
-
}
|
|
240
|
-
/** Creates an observable that subscribes to multiple events */
|
|
290
|
+
return this.model(EventModel, id);
|
|
291
|
+
}
|
|
292
|
+
/** Creates a {@link ReplaceableModel} */
|
|
293
|
+
replaceable(kind, pubkey, identifier) {
|
|
294
|
+
return this.model(ReplaceableModel, kind, pubkey, identifier);
|
|
295
|
+
}
|
|
296
|
+
/** Creates a {@link TimelineModel} */
|
|
297
|
+
timeline(filters, includeOldVersion = false) {
|
|
298
|
+
return this.model(TimelineModel, filters, includeOldVersion);
|
|
299
|
+
}
|
|
300
|
+
/** Creates a {@link EventsModel} */
|
|
241
301
|
events(ids) {
|
|
242
|
-
return
|
|
243
|
-
// lazily get existing events
|
|
244
|
-
defer(() => from(ids.map((id) => this.getEvent(id)))),
|
|
245
|
-
// subscribe to new events
|
|
246
|
-
this.inserts.pipe(filter((e) => ids.includes(e.id))),
|
|
247
|
-
// subscribe to updates
|
|
248
|
-
this.updates.pipe(filter((e) => ids.includes(e.id)))).pipe(
|
|
249
|
-
// ignore empty messages
|
|
250
|
-
filter((e) => !!e),
|
|
251
|
-
// claim all events until cleanup
|
|
252
|
-
claimEvents(this.database),
|
|
253
|
-
// watch for removed events
|
|
254
|
-
mergeWith(this.removes.pipe(filter((e) => ids.includes(e.id)), map((e) => e.id))),
|
|
255
|
-
// merge all events into a directory
|
|
256
|
-
scan((dir, event) => {
|
|
257
|
-
if (typeof event === "string") {
|
|
258
|
-
// delete event by id
|
|
259
|
-
const clone = { ...dir };
|
|
260
|
-
delete clone[event];
|
|
261
|
-
return clone;
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
// add even to directory
|
|
265
|
-
return { ...dir, [event.id]: event };
|
|
266
|
-
}
|
|
267
|
-
}, {}));
|
|
302
|
+
return this.model(EventsModel, ids);
|
|
268
303
|
}
|
|
269
|
-
/** Creates
|
|
270
|
-
replaceable(kind, pubkey, d) {
|
|
271
|
-
let current = undefined;
|
|
272
|
-
return merge(
|
|
273
|
-
// lazily get current event
|
|
274
|
-
defer(() => {
|
|
275
|
-
let event = this.getReplaceable(kind, pubkey, d);
|
|
276
|
-
return event ? of(event) : EMPTY;
|
|
277
|
-
}),
|
|
278
|
-
// subscribe to new events
|
|
279
|
-
this.inserts.pipe(filter((e) => e.pubkey == pubkey && e.kind === kind && (d !== undefined ? getReplaceableIdentifier(e) === d : true)))).pipe(
|
|
280
|
-
// only update if event is newer
|
|
281
|
-
distinctUntilChanged((prev, event) => {
|
|
282
|
-
// are the events the same? i.e. is the prev event older
|
|
283
|
-
return prev.created_at >= event.created_at;
|
|
284
|
-
}),
|
|
285
|
-
// Hacky way to extract the current event so takeUntil can access it
|
|
286
|
-
tap((event) => (current = event)),
|
|
287
|
-
// complete when event is removed
|
|
288
|
-
takeUntil(this.removes.pipe(filter((e) => e.id === current?.id))),
|
|
289
|
-
// emit undefined when removed
|
|
290
|
-
endWith(undefined),
|
|
291
|
-
// keep the observable hot
|
|
292
|
-
repeat(),
|
|
293
|
-
// claim latest event
|
|
294
|
-
claimLatest(this.database));
|
|
295
|
-
}
|
|
296
|
-
/** Creates an observable that subscribes to the latest version of an array of replaceable events*/
|
|
304
|
+
/** Creates a {@link ReplaceableSetModel} */
|
|
297
305
|
replaceableSet(pointers) {
|
|
298
|
-
|
|
299
|
-
return merge(
|
|
300
|
-
// start with existing events
|
|
301
|
-
defer(() => from(pointers.map((p) => this.getReplaceable(p.kind, p.pubkey, p.identifier)))),
|
|
302
|
-
// subscribe to new events
|
|
303
|
-
this.inserts.pipe(filter((e) => isReplaceable(e.kind) && uids.has(getEventUID(e))))).pipe(
|
|
304
|
-
// filter out undefined
|
|
305
|
-
filter((e) => !!e),
|
|
306
|
-
// claim all events
|
|
307
|
-
claimEvents(this.database),
|
|
308
|
-
// convert events to add commands
|
|
309
|
-
map((e) => ["add", e]),
|
|
310
|
-
// watch for removed events
|
|
311
|
-
mergeWith(this.removes.pipe(filter((e) => isReplaceable(e.kind) && uids.has(getEventUID(e))), map((e) => ["remove", e]))),
|
|
312
|
-
// reduce events into directory
|
|
313
|
-
scan((dir, [action, event]) => {
|
|
314
|
-
const uid = getEventUID(event);
|
|
315
|
-
if (action === "add") {
|
|
316
|
-
// add event to dir if its newer
|
|
317
|
-
if (!dir[uid] || dir[uid].created_at < event.created_at)
|
|
318
|
-
return { ...dir, [uid]: event };
|
|
319
|
-
}
|
|
320
|
-
else if (action === "remove" && dir[uid] === event) {
|
|
321
|
-
// remove event from dir
|
|
322
|
-
let newDir = { ...dir };
|
|
323
|
-
delete newDir[uid];
|
|
324
|
-
return newDir;
|
|
325
|
-
}
|
|
326
|
-
return dir;
|
|
327
|
-
}, {}),
|
|
328
|
-
// ignore changes that do not modify the directory
|
|
329
|
-
distinctUntilChanged());
|
|
306
|
+
return this.model(ReplaceableSetModel, pointers);
|
|
330
307
|
}
|
|
331
|
-
/** Creates
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (!keepOldVersions && isReplaceable(event.kind)) {
|
|
363
|
-
const uid = getEventUID(event);
|
|
364
|
-
const existing = seen.get(uid);
|
|
365
|
-
// if this is an older replaceable event, exit
|
|
366
|
-
if (existing && event.created_at < existing.created_at)
|
|
367
|
-
return timeline;
|
|
368
|
-
// update latest version
|
|
369
|
-
seen.set(uid, event);
|
|
370
|
-
// remove old event from timeline
|
|
371
|
-
if (existing)
|
|
372
|
-
newTimeline.slice(newTimeline.indexOf(existing), 1);
|
|
373
|
-
}
|
|
374
|
-
// add event into timeline
|
|
375
|
-
insertEventIntoDescendingList(newTimeline, event);
|
|
376
|
-
return newTimeline;
|
|
377
|
-
}, []),
|
|
378
|
-
// ignore changes that do not modify the timeline instance
|
|
379
|
-
distinctUntilChanged(),
|
|
380
|
-
// hacky hack to clear seen on unsubscribe
|
|
381
|
-
finalize(() => seen.clear()));
|
|
308
|
+
/** Creates a {@link ProfileModel} */
|
|
309
|
+
profile(pubkey) {
|
|
310
|
+
return this.model(ProfileModel, pubkey);
|
|
311
|
+
}
|
|
312
|
+
/** Creates a {@link ContactsModel} */
|
|
313
|
+
contacts(pubkey) {
|
|
314
|
+
return this.model(ContactsModel, pubkey);
|
|
315
|
+
}
|
|
316
|
+
/** Creates a {@link MuteModel} */
|
|
317
|
+
mutes(pubkey) {
|
|
318
|
+
return this.model(MuteModel, pubkey);
|
|
319
|
+
}
|
|
320
|
+
/** Creates a {@link ReactionsModel} */
|
|
321
|
+
reactions(event) {
|
|
322
|
+
return this.model(ReactionsModel, event);
|
|
323
|
+
}
|
|
324
|
+
/** Creates a {@link MailboxesModel} */
|
|
325
|
+
mailboxes(pubkey) {
|
|
326
|
+
return this.model(MailboxesModel, pubkey);
|
|
327
|
+
}
|
|
328
|
+
/** Creates a {@link UserBlossomServersModel} */
|
|
329
|
+
blossomServers(pubkey) {
|
|
330
|
+
return this.model(UserBlossomServersModel, pubkey);
|
|
331
|
+
}
|
|
332
|
+
/** Creates a {@link ThreadModel} */
|
|
333
|
+
thread(root) {
|
|
334
|
+
return this.model(ThreadModel, root);
|
|
335
|
+
}
|
|
336
|
+
/** Creates a {@link CommentsModel} */
|
|
337
|
+
comments(event) {
|
|
338
|
+
return this.model(CommentsModel, event);
|
|
382
339
|
}
|
|
383
340
|
}
|
|
@@ -1,21 +1,58 @@
|
|
|
1
1
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
|
|
2
3
|
import { Observable } from "rxjs";
|
|
3
|
-
|
|
4
|
+
import { LRU } from "../helpers/lru.js";
|
|
5
|
+
import { Mutes } from "../helpers/mutes.js";
|
|
6
|
+
import { ProfileContent } from "../helpers/profile.js";
|
|
7
|
+
import { Thread } from "../models/thread.js";
|
|
8
|
+
/** The read interface for an event store */
|
|
9
|
+
export interface IEventStoreRead {
|
|
10
|
+
/** Check if the event store has an event with id */
|
|
4
11
|
hasEvent(id: string): boolean;
|
|
12
|
+
/** Check if the event store has a replaceable event */
|
|
5
13
|
hasReplaceable(kind: number, pubkey: string, identifier?: string): boolean;
|
|
14
|
+
/** Get an event by id */
|
|
6
15
|
getEvent(id: string): NostrEvent | undefined;
|
|
16
|
+
/** Get a replaceable event */
|
|
7
17
|
getReplaceable(kind: number, pubkey: string, identifier?: string): NostrEvent | undefined;
|
|
18
|
+
/** Get the history of a replaceable event */
|
|
8
19
|
getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[] | undefined;
|
|
9
|
-
|
|
20
|
+
/** Get all events that match the filters */
|
|
21
|
+
getByFilters(filters: Filter | Filter[]): Set<NostrEvent>;
|
|
22
|
+
/** Get a timeline of events that match the filters */
|
|
10
23
|
getTimeline(filters: Filter | Filter[]): NostrEvent[];
|
|
11
24
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
removed
|
|
25
|
+
/** The stream interface for an event store */
|
|
26
|
+
export interface IEventStoreStreams {
|
|
27
|
+
/** A stream of new events added to the store */
|
|
28
|
+
insert$: Observable<NostrEvent>;
|
|
29
|
+
/** A stream of events that have been updated */
|
|
30
|
+
update$: Observable<NostrEvent>;
|
|
31
|
+
/** A stream of events that have been removed */
|
|
32
|
+
remove$: Observable<NostrEvent>;
|
|
33
|
+
}
|
|
34
|
+
/** The actions for an event store */
|
|
35
|
+
export interface IEventStoreActions {
|
|
36
|
+
/** Add an event to the store */
|
|
37
|
+
add(event: NostrEvent): NostrEvent | null;
|
|
38
|
+
/** Remove an event from the store */
|
|
39
|
+
remove(event: string | NostrEvent): boolean;
|
|
40
|
+
/** Notify the store that an event has updated */
|
|
41
|
+
update(event: NostrEvent): void;
|
|
42
|
+
}
|
|
43
|
+
/** The claim interface for an event store */
|
|
44
|
+
export interface IEventClaims {
|
|
45
|
+
/** Sets the claim on the event and touches it */
|
|
46
|
+
claim(event: NostrEvent, claim: any): void;
|
|
47
|
+
/** Checks if an event is claimed by anything */
|
|
48
|
+
isClaimed(event: NostrEvent): boolean;
|
|
49
|
+
/** Removes a claim from an event */
|
|
50
|
+
removeClaim(event: NostrEvent, claim: any): void;
|
|
51
|
+
/** Removes all claims on an event */
|
|
52
|
+
clearClaim(event: NostrEvent): void;
|
|
53
|
+
}
|
|
54
|
+
/** Methods for creating common models */
|
|
55
|
+
export interface IEventStoreModels {
|
|
19
56
|
event(id: string): Observable<NostrEvent | undefined>;
|
|
20
57
|
events(ids: string[]): Observable<Record<string, NostrEvent>>;
|
|
21
58
|
replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
|
|
@@ -25,9 +62,30 @@ export interface IStreamEventStore {
|
|
|
25
62
|
identifier?: string;
|
|
26
63
|
}[]): Observable<Record<string, NostrEvent>>;
|
|
27
64
|
timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
|
|
65
|
+
profile(pubkey: string): Observable<ProfileContent | undefined>;
|
|
66
|
+
contacts(pubkey: string): Observable<ProfilePointer[]>;
|
|
67
|
+
mutes(pubkey: string): Observable<Mutes | undefined>;
|
|
68
|
+
reactions(event: NostrEvent): Observable<NostrEvent[]>;
|
|
69
|
+
mailboxes(pubkey: string): Observable<{
|
|
70
|
+
inboxes: string[];
|
|
71
|
+
outboxes: string[];
|
|
72
|
+
} | undefined>;
|
|
73
|
+
blossomServers(pubkey: string): Observable<URL[]>;
|
|
74
|
+
thread(root: string | EventPointer | AddressPointer): Observable<Thread>;
|
|
28
75
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
76
|
+
/** A computed view of an event set or event store */
|
|
77
|
+
export type Model<T extends unknown> = (events: IEventStore) => Observable<T>;
|
|
78
|
+
/** A constructor for a {@link Model} */
|
|
79
|
+
export type ModelConstructor<T extends unknown, Args extends Array<any>> = ((...args: Args) => Model<T>) & {
|
|
80
|
+
getKey?: (...args: Args) => string;
|
|
81
|
+
};
|
|
82
|
+
/** The base interface for a set of events */
|
|
83
|
+
export interface IEventSet extends IEventStoreRead, IEventStoreStreams, IEventStoreActions, IEventClaims {
|
|
84
|
+
events: LRU<NostrEvent>;
|
|
85
|
+
}
|
|
86
|
+
export interface IEventStore extends IEventStoreRead, IEventStoreStreams, IEventStoreActions, IEventStoreModels, IEventClaims {
|
|
87
|
+
filters(filters: Filter | Filter[]): Observable<NostrEvent>;
|
|
88
|
+
updated(id: string | NostrEvent): Observable<NostrEvent>;
|
|
89
|
+
removed(id: string): Observable<never>;
|
|
90
|
+
model<T extends unknown, Args extends Array<any>>(constructor: ModelConstructor<T, Args>, ...args: Args): Observable<T>;
|
|
33
91
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
|
|
3
|
+
import { DecodeResult } from "./pointers.js";
|
|
4
|
+
export type HandlerLinkPlatform = "web" | "ios" | "android";
|
|
5
|
+
export type HandlerLinkType = DecodeResult["type"];
|
|
6
|
+
/** Returns an array of supported kinds for a given handler */
|
|
7
|
+
export declare function getHandlerSupportedKinds(handler: NostrEvent): number[];
|
|
8
|
+
/** Returns the name of the handler */
|
|
9
|
+
export declare function getHandlerName(handler: NostrEvent): string;
|
|
10
|
+
/** Returns the picture of the handler */
|
|
11
|
+
export declare function getHandlerPicture(handler: NostrEvent, fallback?: string): string | undefined;
|
|
12
|
+
/** Returns the description of the handler */
|
|
13
|
+
export declare function getHandlerDescription(handler: NostrEvent): string | undefined;
|
|
14
|
+
/** Returns the web link template for a handler and type */
|
|
15
|
+
export declare function getHandlerLinkTemplate(handler: NostrEvent, platform?: HandlerLinkPlatform, type?: HandlerLinkType): string | undefined;
|
|
16
|
+
/** Returns a link for a Profile Pointer */
|
|
17
|
+
export declare function createHandlerProfileLink(handler: NostrEvent, pointer: ProfilePointer, platform?: HandlerLinkPlatform): string | undefined;
|
|
18
|
+
/** Returns a link for an Event Pointer */
|
|
19
|
+
export declare function createHandlerEventLink(handler: NostrEvent, pointer: EventPointer, platform?: HandlerLinkPlatform): string | undefined;
|
|
20
|
+
/** Returns a link for an Address Pointer */
|
|
21
|
+
export declare function createHandlerAddressLink(handler: NostrEvent, pointer: AddressPointer, platform?: HandlerLinkPlatform): string | undefined;
|
|
22
|
+
/** Creates a handler link for a pointer and optionally fallsback to a web link */
|
|
23
|
+
export declare function createHandlerLink(handler: NostrEvent, pointer: AddressPointer | EventPointer | ProfilePointer, platform?: HandlerLinkPlatform, webFallback?: boolean): string | undefined;
|