applesauce-core 1.0.0 → 2.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/README.md +7 -13
- package/dist/event-store/{database.d.ts → event-set.d.ts} +35 -20
- package/dist/event-store/{database.js → event-set.js} +91 -60
- package/dist/event-store/event-store.d.ts +64 -25
- package/dist/event-store/event-store.js +163 -206
- 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/blossom.d.ts +2 -0
- package/dist/helpers/blossom.js +18 -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 +4 -1
- package/dist/helpers/event.js +13 -3
- 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 +1 -1
- package/dist/helpers/lists.js +2 -2
- 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 +5 -1
- package/dist/observable/index.js +6 -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__/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__/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__/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/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__/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
package/README.md
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# applesauce-core
|
|
2
2
|
|
|
3
|
-
AppleSauce is a collection of utilities for building reactive nostr applications. The core package provides an in-memory event database and reactive
|
|
3
|
+
AppleSauce is a collection of utilities for building reactive nostr applications. The core package provides an in-memory event database and reactive models to help you build nostr UIs with less code.
|
|
4
4
|
|
|
5
5
|
## Key Components
|
|
6
6
|
|
|
7
7
|
- **Helpers**: Core utility methods for parsing and extracting data from nostr events
|
|
8
8
|
- **EventStore**: In-memory database for storing and subscribing to nostr events
|
|
9
|
-
- **
|
|
10
|
-
- **Queries**: Complex subscriptions for common nostr data patterns
|
|
9
|
+
- **Models**: Complex subscriptions for common nostr data patterns
|
|
11
10
|
|
|
12
11
|
## Documentation
|
|
13
12
|
|
|
@@ -19,15 +18,13 @@ For detailed documentation and guides, visit:
|
|
|
19
18
|
## Example
|
|
20
19
|
|
|
21
20
|
```js
|
|
22
|
-
import { EventStore
|
|
21
|
+
import { EventStore } from "applesauce-core";
|
|
22
|
+
import { ProfileModel, TimelineModel } from "applesauce-core/models";
|
|
23
23
|
import { Relay } from "nostr-tools/relay";
|
|
24
24
|
|
|
25
25
|
// Create a single EventStore instance for your app
|
|
26
26
|
const eventStore = new EventStore();
|
|
27
27
|
|
|
28
|
-
// Create a QueryStore to manage subscriptions efficiently
|
|
29
|
-
const queryStore = new QueryStore(eventStore);
|
|
30
|
-
|
|
31
28
|
// Use any nostr library for relay connections (nostr-tools, ndk, nostrify, etc...)
|
|
32
29
|
const relay = await Relay.connect("wss://relay.example.com");
|
|
33
30
|
|
|
@@ -38,18 +35,15 @@ const sub = relay.subscribe([{ authors: ["3bf0c63fcb93463407af97a5e5ee64fa883d10
|
|
|
38
35
|
},
|
|
39
36
|
});
|
|
40
37
|
|
|
41
|
-
// Subscribe to profile changes using
|
|
42
|
-
const profile =
|
|
43
|
-
ProfileQuery,
|
|
44
|
-
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
|
45
|
-
);
|
|
38
|
+
// Subscribe to profile changes using ProfileModel
|
|
39
|
+
const profile = eventStore.model(ProfileModel, "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d");
|
|
46
40
|
|
|
47
41
|
profile.subscribe((parsed) => {
|
|
48
42
|
if (parsed) console.log(parsed);
|
|
49
43
|
});
|
|
50
44
|
|
|
51
45
|
// Subscribe to a timeline of events
|
|
52
|
-
const timeline =
|
|
46
|
+
const timeline = eventStore.model(TimelineModel, { kinds: [1] });
|
|
53
47
|
|
|
54
48
|
timeline.subscribe((events) => {
|
|
55
49
|
console.log(events);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
2
|
import { Subject } from "rxjs";
|
|
3
3
|
import { LRU } from "../helpers/lru.js";
|
|
4
|
+
import { IEventSet } from "./interface.js";
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
* NOTE: does not handle replaceable events
|
|
6
|
+
* A set of nostr events that can be queried and subscribed to
|
|
7
|
+
* NOTE: does not handle replaceable events or any deletion logic
|
|
7
8
|
*/
|
|
8
|
-
export declare class
|
|
9
|
+
export declare class EventSet implements IEventSet {
|
|
9
10
|
protected log: import("debug").Debugger;
|
|
10
11
|
/** Indexes */
|
|
11
12
|
protected kinds: Map<number, Set<import("nostr-tools").Event>>;
|
|
@@ -17,50 +18,64 @@ export declare class Database {
|
|
|
17
18
|
/** A sorted array of replaceable events by uid */
|
|
18
19
|
protected replaceable: Map<string, import("nostr-tools").Event[]>;
|
|
19
20
|
/** A stream of events inserted into the database */
|
|
20
|
-
|
|
21
|
+
insert$: Subject<import("nostr-tools").Event>;
|
|
21
22
|
/** A stream of events that have been updated */
|
|
22
|
-
|
|
23
|
+
update$: Subject<import("nostr-tools").Event>;
|
|
23
24
|
/** A stream of events removed from the database */
|
|
24
|
-
|
|
25
|
+
remove$: Subject<import("nostr-tools").Event>;
|
|
25
26
|
/** A method thats called before a new event is inserted */
|
|
26
|
-
onBeforeInsert?: (event: NostrEvent) =>
|
|
27
|
+
onBeforeInsert?: (event: NostrEvent) => boolean;
|
|
28
|
+
/** The number of events in the event set */
|
|
27
29
|
get size(): number;
|
|
28
|
-
protected claims: WeakMap<import("nostr-tools").Event, any>;
|
|
29
|
-
/** Index helper methods */
|
|
30
|
-
protected getKindIndex(kind: number): Set<import("nostr-tools").Event>;
|
|
31
|
-
protected getAuthorsIndex(author: string): Set<import("nostr-tools").Event>;
|
|
32
|
-
protected getTagIndex(tagAndValue: string): Set<import("nostr-tools").Event>;
|
|
33
30
|
/** Moves an event to the top of the LRU cache */
|
|
34
31
|
touch(event: NostrEvent): void;
|
|
35
32
|
/** Checks if the database contains an event without touching it */
|
|
36
33
|
hasEvent(id: string): boolean;
|
|
37
34
|
/** Gets a single event based on id */
|
|
38
35
|
getEvent(id: string): NostrEvent | undefined;
|
|
39
|
-
/** Checks if the
|
|
40
|
-
hasReplaceable(kind: number, pubkey: string,
|
|
41
|
-
/** Gets
|
|
42
|
-
getReplaceable(kind: number, pubkey: string,
|
|
36
|
+
/** Checks if the event set has a replaceable event */
|
|
37
|
+
hasReplaceable(kind: number, pubkey: string, identifier?: string): boolean;
|
|
38
|
+
/** Gets the latest replaceable event */
|
|
39
|
+
getReplaceable(kind: number, pubkey: string, identifier?: string): NostrEvent | undefined;
|
|
40
|
+
/** Gets the history of a replaceable event */
|
|
41
|
+
getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[] | undefined;
|
|
42
|
+
/** Gets all events that match the filters */
|
|
43
|
+
getByFilters(filters: Filter | Filter[]): Set<NostrEvent>;
|
|
44
|
+
/** Gets a timeline of events that match the filters */
|
|
45
|
+
getTimeline(filters: Filter | Filter[]): NostrEvent[];
|
|
43
46
|
/** Inserts an event into the database and notifies all subscriptions */
|
|
44
|
-
|
|
47
|
+
add(event: NostrEvent): NostrEvent | null;
|
|
45
48
|
/** Inserts and event into the database and notifies all subscriptions that the event has updated */
|
|
46
|
-
|
|
49
|
+
update(event: NostrEvent): boolean;
|
|
47
50
|
/** Removes an event from the database and notifies all subscriptions */
|
|
48
|
-
|
|
51
|
+
remove(eventOrId: string | NostrEvent): boolean;
|
|
52
|
+
/** A weak map of events that are claimed by other things */
|
|
53
|
+
protected claims: WeakMap<import("nostr-tools").Event, any>;
|
|
49
54
|
/** Sets the claim on the event and touches it */
|
|
50
|
-
|
|
55
|
+
claim(event: NostrEvent, claim: any): void;
|
|
51
56
|
/** Checks if an event is claimed by anything */
|
|
52
57
|
isClaimed(event: NostrEvent): boolean;
|
|
53
58
|
/** Removes a claim from an event */
|
|
54
59
|
removeClaim(event: NostrEvent, claim: any): void;
|
|
55
60
|
/** Removes all claims on an event */
|
|
56
61
|
clearClaim(event: NostrEvent): void;
|
|
62
|
+
/** Index helper methods */
|
|
63
|
+
protected getKindIndex(kind: number): Set<import("nostr-tools").Event>;
|
|
64
|
+
protected getAuthorsIndex(author: string): Set<import("nostr-tools").Event>;
|
|
65
|
+
protected getTagIndex(tagAndValue: string): Set<import("nostr-tools").Event>;
|
|
66
|
+
/** Iterates over all events by author */
|
|
57
67
|
iterateAuthors(authors: Iterable<string>): Generator<NostrEvent>;
|
|
68
|
+
/** Iterates over all events by indexable tag and value */
|
|
58
69
|
iterateTag(tag: string, values: Iterable<string>): Generator<NostrEvent>;
|
|
70
|
+
/** Iterates over all events by kind */
|
|
59
71
|
iterateKinds(kinds: Iterable<number>): Generator<NostrEvent>;
|
|
72
|
+
/** Iterates over all events by time */
|
|
60
73
|
iterateTime(since: number | undefined, until: number | undefined): Generator<NostrEvent>;
|
|
74
|
+
/** Iterates over all events by id */
|
|
61
75
|
iterateIds(ids: Iterable<string>): Generator<NostrEvent>;
|
|
62
76
|
/** Returns all events that match the filter */
|
|
63
77
|
getEventsForFilter(filter: Filter): Set<NostrEvent>;
|
|
78
|
+
/** Returns all events that match the filters */
|
|
64
79
|
getEventsForFilters(filters: Filter[]): Set<NostrEvent>;
|
|
65
80
|
/** Remove the oldest events that are not claimed */
|
|
66
81
|
prune(limit?: number): number;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { binarySearch, insertEventIntoDescendingList } from "nostr-tools/utils";
|
|
2
2
|
import { Subject } from "rxjs";
|
|
3
|
-
import { getEventUID, getIndexableTags,
|
|
4
|
-
import { INDEXABLE_TAGS } from "./common.js";
|
|
5
|
-
import { logger } from "../logger.js";
|
|
3
|
+
import { createReplaceableAddress, getEventUID, getIndexableTags, isReplaceable } from "../helpers/event.js";
|
|
6
4
|
import { LRU } from "../helpers/lru.js";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
6
|
+
import { INDEXABLE_TAGS } from "./common.js";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* NOTE: does not handle replaceable events
|
|
8
|
+
* A set of nostr events that can be queried and subscribed to
|
|
9
|
+
* NOTE: does not handle replaceable events or any deletion logic
|
|
10
10
|
*/
|
|
11
|
-
export class
|
|
12
|
-
log = logger.extend("
|
|
11
|
+
export class EventSet {
|
|
12
|
+
log = logger.extend("EventSet");
|
|
13
13
|
/** Indexes */
|
|
14
14
|
kinds = new Map();
|
|
15
15
|
authors = new Map();
|
|
@@ -20,45 +20,17 @@ export class Database {
|
|
|
20
20
|
/** A sorted array of replaceable events by uid */
|
|
21
21
|
replaceable = new Map();
|
|
22
22
|
/** A stream of events inserted into the database */
|
|
23
|
-
|
|
23
|
+
insert$ = new Subject();
|
|
24
24
|
/** A stream of events that have been updated */
|
|
25
|
-
|
|
25
|
+
update$ = new Subject();
|
|
26
26
|
/** A stream of events removed from the database */
|
|
27
|
-
|
|
27
|
+
remove$ = new Subject();
|
|
28
28
|
/** A method thats called before a new event is inserted */
|
|
29
29
|
onBeforeInsert;
|
|
30
|
+
/** The number of events in the event set */
|
|
30
31
|
get size() {
|
|
31
32
|
return this.events.size;
|
|
32
33
|
}
|
|
33
|
-
claims = new WeakMap();
|
|
34
|
-
/** Index helper methods */
|
|
35
|
-
getKindIndex(kind) {
|
|
36
|
-
if (!this.kinds.has(kind))
|
|
37
|
-
this.kinds.set(kind, new Set());
|
|
38
|
-
return this.kinds.get(kind);
|
|
39
|
-
}
|
|
40
|
-
getAuthorsIndex(author) {
|
|
41
|
-
if (!this.authors.has(author))
|
|
42
|
-
this.authors.set(author, new Set());
|
|
43
|
-
return this.authors.get(author);
|
|
44
|
-
}
|
|
45
|
-
getTagIndex(tagAndValue) {
|
|
46
|
-
if (!this.tags.has(tagAndValue)) {
|
|
47
|
-
// build new tag index from existing events
|
|
48
|
-
const events = new Set();
|
|
49
|
-
const ts = Date.now();
|
|
50
|
-
for (const event of this.events.values()) {
|
|
51
|
-
if (getIndexableTags(event).has(tagAndValue)) {
|
|
52
|
-
events.add(event);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const took = Date.now() - ts;
|
|
56
|
-
if (took > 100)
|
|
57
|
-
this.log(`Built index ${tagAndValue} took ${took}ms`);
|
|
58
|
-
this.tags.set(tagAndValue, events);
|
|
59
|
-
}
|
|
60
|
-
return this.tags.get(tagAndValue);
|
|
61
|
-
}
|
|
62
34
|
/** Moves an event to the top of the LRU cache */
|
|
63
35
|
touch(event) {
|
|
64
36
|
this.events.set(event.id, event);
|
|
@@ -71,33 +43,54 @@ export class Database {
|
|
|
71
43
|
getEvent(id) {
|
|
72
44
|
return this.events.get(id);
|
|
73
45
|
}
|
|
74
|
-
/** Checks if the
|
|
75
|
-
hasReplaceable(kind, pubkey,
|
|
76
|
-
const events = this.replaceable.get(createReplaceableAddress(kind, pubkey,
|
|
46
|
+
/** Checks if the event set has a replaceable event */
|
|
47
|
+
hasReplaceable(kind, pubkey, identifier) {
|
|
48
|
+
const events = this.replaceable.get(createReplaceableAddress(kind, pubkey, identifier));
|
|
77
49
|
return !!events && events.length > 0;
|
|
78
50
|
}
|
|
79
|
-
/** Gets
|
|
80
|
-
getReplaceable(kind, pubkey,
|
|
81
|
-
|
|
51
|
+
/** Gets the latest replaceable event */
|
|
52
|
+
getReplaceable(kind, pubkey, identifier) {
|
|
53
|
+
const address = createReplaceableAddress(kind, pubkey, identifier);
|
|
54
|
+
const events = this.replaceable.get(address);
|
|
55
|
+
return events?.[0];
|
|
56
|
+
}
|
|
57
|
+
/** Gets the history of a replaceable event */
|
|
58
|
+
getReplaceableHistory(kind, pubkey, identifier) {
|
|
59
|
+
const address = createReplaceableAddress(kind, pubkey, identifier);
|
|
60
|
+
return this.replaceable.get(address);
|
|
61
|
+
}
|
|
62
|
+
/** Gets all events that match the filters */
|
|
63
|
+
getByFilters(filters) {
|
|
64
|
+
return this.getEventsForFilters(Array.isArray(filters) ? filters : [filters]);
|
|
65
|
+
}
|
|
66
|
+
/** Gets a timeline of events that match the filters */
|
|
67
|
+
getTimeline(filters) {
|
|
68
|
+
const timeline = [];
|
|
69
|
+
const events = this.getEventsForFilters(Array.isArray(filters) ? filters : [filters]);
|
|
70
|
+
for (const event of events)
|
|
71
|
+
insertEventIntoDescendingList(timeline, event);
|
|
72
|
+
return timeline;
|
|
82
73
|
}
|
|
83
74
|
/** Inserts an event into the database and notifies all subscriptions */
|
|
84
|
-
|
|
75
|
+
add(event) {
|
|
85
76
|
const id = event.id;
|
|
86
77
|
const current = this.events.get(id);
|
|
87
78
|
if (current)
|
|
88
79
|
return current;
|
|
89
|
-
|
|
80
|
+
// Ignore events if before insert returns false
|
|
81
|
+
if (this.onBeforeInsert?.(event) === false)
|
|
82
|
+
return null;
|
|
90
83
|
this.events.set(id, event);
|
|
91
84
|
this.getKindIndex(event.kind).add(event);
|
|
92
85
|
this.getAuthorsIndex(event.pubkey).add(event);
|
|
86
|
+
// Add the event to the tag indexes if they exist
|
|
93
87
|
for (const tag of getIndexableTags(event)) {
|
|
94
|
-
if (this.tags.has(tag))
|
|
88
|
+
if (this.tags.has(tag))
|
|
95
89
|
this.getTagIndex(tag).add(event);
|
|
96
|
-
}
|
|
97
90
|
}
|
|
98
|
-
//
|
|
91
|
+
// Insert into time index
|
|
99
92
|
insertEventIntoDescendingList(this.created_at, event);
|
|
100
|
-
//
|
|
93
|
+
// Insert into replaceable index
|
|
101
94
|
if (isReplaceable(event.kind)) {
|
|
102
95
|
const uid = getEventUID(event);
|
|
103
96
|
let array = this.replaceable.get(uid);
|
|
@@ -109,17 +102,19 @@ export class Database {
|
|
|
109
102
|
// insert the event into the sorted array
|
|
110
103
|
insertEventIntoDescendingList(array, event);
|
|
111
104
|
}
|
|
112
|
-
|
|
105
|
+
// Notify subscribers that the event was inserted
|
|
106
|
+
this.insert$.next(event);
|
|
113
107
|
return event;
|
|
114
108
|
}
|
|
115
109
|
/** Inserts and event into the database and notifies all subscriptions that the event has updated */
|
|
116
|
-
|
|
117
|
-
const inserted = this.
|
|
118
|
-
|
|
119
|
-
|
|
110
|
+
update(event) {
|
|
111
|
+
const inserted = this.add(event);
|
|
112
|
+
if (inserted)
|
|
113
|
+
this.update$.next(inserted);
|
|
114
|
+
return inserted !== null;
|
|
120
115
|
}
|
|
121
116
|
/** Removes an event from the database and notifies all subscriptions */
|
|
122
|
-
|
|
117
|
+
remove(eventOrId) {
|
|
123
118
|
let event = typeof eventOrId === "string" ? this.events.get(eventOrId) : eventOrId;
|
|
124
119
|
if (!event)
|
|
125
120
|
throw new Error("Missing event");
|
|
@@ -150,11 +145,13 @@ export class Database {
|
|
|
150
145
|
// remove any claims this event has
|
|
151
146
|
this.claims.delete(event);
|
|
152
147
|
// notify subscribers this event was removed
|
|
153
|
-
this.
|
|
148
|
+
this.remove$.next(event);
|
|
154
149
|
return true;
|
|
155
150
|
}
|
|
151
|
+
/** A weak map of events that are claimed by other things */
|
|
152
|
+
claims = new WeakMap();
|
|
156
153
|
/** Sets the claim on the event and touches it */
|
|
157
|
-
|
|
154
|
+
claim(event, claim) {
|
|
158
155
|
if (!this.claims.has(event)) {
|
|
159
156
|
this.claims.set(event, claim);
|
|
160
157
|
}
|
|
@@ -175,6 +172,35 @@ export class Database {
|
|
|
175
172
|
clearClaim(event) {
|
|
176
173
|
this.claims.delete(event);
|
|
177
174
|
}
|
|
175
|
+
/** Index helper methods */
|
|
176
|
+
getKindIndex(kind) {
|
|
177
|
+
if (!this.kinds.has(kind))
|
|
178
|
+
this.kinds.set(kind, new Set());
|
|
179
|
+
return this.kinds.get(kind);
|
|
180
|
+
}
|
|
181
|
+
getAuthorsIndex(author) {
|
|
182
|
+
if (!this.authors.has(author))
|
|
183
|
+
this.authors.set(author, new Set());
|
|
184
|
+
return this.authors.get(author);
|
|
185
|
+
}
|
|
186
|
+
getTagIndex(tagAndValue) {
|
|
187
|
+
if (!this.tags.has(tagAndValue)) {
|
|
188
|
+
// build new tag index from existing events
|
|
189
|
+
const events = new Set();
|
|
190
|
+
const ts = Date.now();
|
|
191
|
+
for (const event of this.events.values()) {
|
|
192
|
+
if (getIndexableTags(event).has(tagAndValue)) {
|
|
193
|
+
events.add(event);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const took = Date.now() - ts;
|
|
197
|
+
if (took > 100)
|
|
198
|
+
this.log(`Built index ${tagAndValue} took ${took}ms`);
|
|
199
|
+
this.tags.set(tagAndValue, events);
|
|
200
|
+
}
|
|
201
|
+
return this.tags.get(tagAndValue);
|
|
202
|
+
}
|
|
203
|
+
/** Iterates over all events by author */
|
|
178
204
|
*iterateAuthors(authors) {
|
|
179
205
|
for (const author of authors) {
|
|
180
206
|
const events = this.authors.get(author);
|
|
@@ -184,6 +210,7 @@ export class Database {
|
|
|
184
210
|
}
|
|
185
211
|
}
|
|
186
212
|
}
|
|
213
|
+
/** Iterates over all events by indexable tag and value */
|
|
187
214
|
*iterateTag(tag, values) {
|
|
188
215
|
for (const value of values) {
|
|
189
216
|
const events = this.getTagIndex(tag + ":" + value);
|
|
@@ -193,6 +220,7 @@ export class Database {
|
|
|
193
220
|
}
|
|
194
221
|
}
|
|
195
222
|
}
|
|
223
|
+
/** Iterates over all events by kind */
|
|
196
224
|
*iterateKinds(kinds) {
|
|
197
225
|
for (const kind of kinds) {
|
|
198
226
|
const events = this.kinds.get(kind);
|
|
@@ -202,6 +230,7 @@ export class Database {
|
|
|
202
230
|
}
|
|
203
231
|
}
|
|
204
232
|
}
|
|
233
|
+
/** Iterates over all events by time */
|
|
205
234
|
*iterateTime(since, until) {
|
|
206
235
|
let untilIndex = 0;
|
|
207
236
|
let sinceIndex = this.created_at.length - 1;
|
|
@@ -223,6 +252,7 @@ export class Database {
|
|
|
223
252
|
yield this.created_at[i];
|
|
224
253
|
}
|
|
225
254
|
}
|
|
255
|
+
/** Iterates over all events by id */
|
|
226
256
|
*iterateIds(ids) {
|
|
227
257
|
for (const id of ids) {
|
|
228
258
|
if (this.events.has(id))
|
|
@@ -286,6 +316,7 @@ export class Database {
|
|
|
286
316
|
}
|
|
287
317
|
return events;
|
|
288
318
|
}
|
|
319
|
+
/** Returns all events that match the filters */
|
|
289
320
|
getEventsForFilters(filters) {
|
|
290
321
|
if (filters.length === 0)
|
|
291
322
|
throw new Error("No Filters");
|
|
@@ -304,7 +335,7 @@ export class Database {
|
|
|
304
335
|
while (cursor) {
|
|
305
336
|
const event = cursor.value;
|
|
306
337
|
if (!this.isClaimed(event)) {
|
|
307
|
-
this.
|
|
338
|
+
this.remove(event);
|
|
308
339
|
removed++;
|
|
309
340
|
if (removed >= limit)
|
|
310
341
|
break;
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
2
|
import { Observable } from "rxjs";
|
|
3
|
-
import {
|
|
4
|
-
import { IEventStore } from "./interface.js";
|
|
3
|
+
import { EventSet } from "./event-set.js";
|
|
4
|
+
import { IEventStore, ModelConstructor } from "./interface.js";
|
|
5
|
+
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
6
|
+
/** A symbol on an event that marks which event store its part of */
|
|
5
7
|
export declare const EventStoreSymbol: unique symbol;
|
|
6
8
|
export declare class EventStore implements IEventStore {
|
|
7
|
-
database:
|
|
9
|
+
database: EventSet;
|
|
8
10
|
/** Enable this to keep old versions of replaceable events */
|
|
9
11
|
keepOldVersions: boolean;
|
|
10
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* A method used to verify new events before added them
|
|
14
|
+
* @returns true if the event is valid, false if it should be ignored
|
|
15
|
+
*/
|
|
11
16
|
verifyEvent?: (event: NostrEvent) => boolean;
|
|
12
17
|
/** A stream of new events added to the store */
|
|
13
|
-
|
|
18
|
+
insert$: Observable<NostrEvent>;
|
|
14
19
|
/** A stream of events that have been updated */
|
|
15
|
-
|
|
20
|
+
update$: Observable<NostrEvent>;
|
|
16
21
|
/** A stream of events that have been removed */
|
|
17
|
-
|
|
22
|
+
remove$: Observable<NostrEvent>;
|
|
18
23
|
constructor();
|
|
19
24
|
protected deletedIds: Set<string>;
|
|
20
25
|
protected deletedCoords: Map<string, number>;
|
|
@@ -23,31 +28,46 @@ export declare class EventStore implements IEventStore {
|
|
|
23
28
|
/** Copies important metadata from and identical event to another */
|
|
24
29
|
static mergeDuplicateEvent(source: NostrEvent, dest: NostrEvent): void;
|
|
25
30
|
/**
|
|
26
|
-
* Adds an event to the
|
|
27
|
-
* @
|
|
31
|
+
* Adds an event to the store and update subscriptions
|
|
32
|
+
* @returns The existing event or the event that was added, if it was ignored returns null
|
|
28
33
|
*/
|
|
29
|
-
add(event: NostrEvent, fromRelay?: string): NostrEvent;
|
|
34
|
+
add(event: NostrEvent, fromRelay?: string): NostrEvent | null;
|
|
30
35
|
/** Removes an event from the database and updates subscriptions */
|
|
31
36
|
remove(event: string | NostrEvent): boolean;
|
|
37
|
+
/** Add an event to the store and notifies all subscribes it has updated */
|
|
38
|
+
update(event: NostrEvent): boolean;
|
|
32
39
|
/** Removes any event that is not being used by a subscription */
|
|
33
40
|
prune(max?: number): number;
|
|
34
|
-
/**
|
|
35
|
-
update(event: NostrEvent): NostrEvent;
|
|
36
|
-
/** Get all events matching a filter */
|
|
37
|
-
getAll(filters: Filter | Filter[]): Set<NostrEvent>;
|
|
38
|
-
/** Check if the store has an event */
|
|
41
|
+
/** Check if the store has an event by id */
|
|
39
42
|
hasEvent(id: string): boolean;
|
|
43
|
+
/** Get an event by id from the store */
|
|
40
44
|
getEvent(id: string): NostrEvent | undefined;
|
|
41
45
|
/** Check if the store has a replaceable event */
|
|
42
46
|
hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
|
|
43
47
|
/** Gets the latest version of a replaceable event */
|
|
44
|
-
getReplaceable(kind: number, pubkey: string,
|
|
48
|
+
getReplaceable(kind: number, pubkey: string, identifier?: string): NostrEvent | undefined;
|
|
45
49
|
/** Returns all versions of a replaceable event */
|
|
46
|
-
getReplaceableHistory(kind: number, pubkey: string,
|
|
50
|
+
getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[] | undefined;
|
|
51
|
+
/** Get all events matching a filter */
|
|
52
|
+
getByFilters(filters: Filter | Filter[]): Set<NostrEvent>;
|
|
47
53
|
/** Returns a timeline of events that match filters */
|
|
48
54
|
getTimeline(filters: Filter | Filter[]): NostrEvent[];
|
|
55
|
+
/** Sets the claim on the event and touches it */
|
|
56
|
+
claim(event: NostrEvent, claim: any): void;
|
|
57
|
+
/** Checks if an event is claimed by anything */
|
|
58
|
+
isClaimed(event: NostrEvent): boolean;
|
|
59
|
+
/** Removes a claim from an event */
|
|
60
|
+
removeClaim(event: NostrEvent, claim: any): void;
|
|
61
|
+
/** Removes all claims on an event */
|
|
62
|
+
clearClaim(event: NostrEvent): void;
|
|
63
|
+
/** A directory of all active models */
|
|
64
|
+
protected models: Map<ModelConstructor<any, any[]>, Map<string, Observable<any>>>;
|
|
65
|
+
/** How long a model should be kept "warm" while nothing is subscribed to it */
|
|
66
|
+
modelKeepWarm: number;
|
|
67
|
+
/** Get or create a model on the event store */
|
|
68
|
+
model<T extends unknown, Args extends Array<any>>(constructor: ModelConstructor<T, Args>, ...args: Args): Observable<T>;
|
|
49
69
|
/**
|
|
50
|
-
* Creates an observable that streams all events that match the filter
|
|
70
|
+
* Creates an observable that streams all events that match the filter
|
|
51
71
|
* @param filters
|
|
52
72
|
* @param [onlyNew=false] Only subscribe to new events
|
|
53
73
|
*/
|
|
@@ -56,18 +76,37 @@ export declare class EventStore implements IEventStore {
|
|
|
56
76
|
removed(id: string): Observable<never>;
|
|
57
77
|
/** Creates an observable that emits when event is updated */
|
|
58
78
|
updated(event: string | NostrEvent): Observable<NostrEvent>;
|
|
59
|
-
/** Creates
|
|
79
|
+
/** Creates a {@link EventModel} */
|
|
60
80
|
event(id: string): Observable<NostrEvent | undefined>;
|
|
61
|
-
/** Creates
|
|
81
|
+
/** Creates a {@link ReplaceableModel} */
|
|
82
|
+
replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
|
|
83
|
+
/** Creates a {@link TimelineModel} */
|
|
84
|
+
timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
|
|
85
|
+
/** Creates a {@link EventsModel} */
|
|
62
86
|
events(ids: string[]): Observable<Record<string, NostrEvent>>;
|
|
63
|
-
/** Creates
|
|
64
|
-
replaceable(kind: number, pubkey: string, d?: string): Observable<NostrEvent | undefined>;
|
|
65
|
-
/** Creates an observable that subscribes to the latest version of an array of replaceable events*/
|
|
87
|
+
/** Creates a {@link ReplaceableSetModel} */
|
|
66
88
|
replaceableSet(pointers: {
|
|
67
89
|
kind: number;
|
|
68
90
|
pubkey: string;
|
|
69
91
|
identifier?: string;
|
|
70
92
|
}[]): Observable<Record<string, NostrEvent>>;
|
|
71
|
-
/** Creates
|
|
72
|
-
|
|
93
|
+
/** Creates a {@link ProfileModel} */
|
|
94
|
+
profile(pubkey: string): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
|
|
95
|
+
/** Creates a {@link ContactsModel} */
|
|
96
|
+
contacts(pubkey: string): Observable<import("nostr-tools/nip19").ProfilePointer[]>;
|
|
97
|
+
/** Creates a {@link MuteModel} */
|
|
98
|
+
mutes(pubkey: string): Observable<import("../helpers/mutes.js").Mutes | undefined>;
|
|
99
|
+
/** Creates a {@link ReactionsModel} */
|
|
100
|
+
reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
|
|
101
|
+
/** Creates a {@link MailboxesModel} */
|
|
102
|
+
mailboxes(pubkey: string): Observable<{
|
|
103
|
+
inboxes: string[];
|
|
104
|
+
outboxes: string[];
|
|
105
|
+
} | undefined>;
|
|
106
|
+
/** Creates a {@link UserBlossomServersModel} */
|
|
107
|
+
blossomServers(pubkey: string): Observable<URL[]>;
|
|
108
|
+
/** Creates a {@link ThreadModel} */
|
|
109
|
+
thread(root: string | EventPointer | AddressPointer): Observable<import("../models/thread.js").Thread>;
|
|
110
|
+
/** Creates a {@link CommentsModel} */
|
|
111
|
+
comments(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
|
|
73
112
|
}
|