applesauce-core 0.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/LICENSE +21 -0
- package/dist/event-store/common.d.ts +1 -0
- package/dist/event-store/common.js +2 -0
- package/dist/event-store/database.d.ts +34 -0
- package/dist/event-store/database.js +240 -0
- package/dist/event-store/event-store.d.ts +21 -0
- package/dist/event-store/event-store.js +80 -0
- package/dist/event-store/index.d.ts +1 -0
- package/dist/event-store/index.js +1 -0
- package/dist/helpers/event.d.ts +6 -0
- package/dist/helpers/event.js +34 -0
- package/dist/helpers/filter.d.ts +3 -0
- package/dist/helpers/filter.js +38 -0
- package/dist/helpers/index.d.ts +5 -0
- package/dist/helpers/index.js +5 -0
- package/dist/helpers/mailboxes.d.ts +3 -0
- package/dist/helpers/mailboxes.js +30 -0
- package/dist/helpers/profile.d.ts +18 -0
- package/dist/helpers/profile.js +28 -0
- package/dist/helpers/relays.d.ts +6 -0
- package/dist/helpers/relays.js +30 -0
- package/dist/helpers/symbols.d.ts +17 -0
- package/dist/helpers/symbols.js +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +2 -0
- package/dist/observable/index.d.ts +2 -0
- package/dist/observable/index.js +2 -0
- package/dist/observable/stateful.d.ts +10 -0
- package/dist/observable/stateful.js +60 -0
- package/dist/observable/throttle.d.ts +2 -0
- package/dist/observable/throttle.js +22 -0
- package/dist/promise/deferred.d.ts +5 -0
- package/dist/promise/deferred.js +14 -0
- package/dist/promise/index.d.ts +1 -0
- package/dist/promise/index.js +1 -0
- package/dist/query-store/index.d.ts +26 -0
- package/dist/query-store/index.js +71 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 hzrd149
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const INDEXABLE_TAGS: Set<string>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { LRU } from "tiny-lru";
|
|
3
|
+
export declare class Database {
|
|
4
|
+
log: import("debug").Debugger;
|
|
5
|
+
/** Max number of events to hold */
|
|
6
|
+
max?: number;
|
|
7
|
+
/** Indexes */
|
|
8
|
+
kinds: Map<number, Set<import("nostr-tools").Event>>;
|
|
9
|
+
authors: Map<string, Set<import("nostr-tools").Event>>;
|
|
10
|
+
tags: LRU<Set<import("nostr-tools").Event>>;
|
|
11
|
+
created_at: NostrEvent[];
|
|
12
|
+
/** LRU cache of last events touched */
|
|
13
|
+
events: LRU<import("nostr-tools").Event>;
|
|
14
|
+
constructor(max?: number);
|
|
15
|
+
/** Index helper methods */
|
|
16
|
+
private getKindIndex;
|
|
17
|
+
private getAuthorsIndex;
|
|
18
|
+
private getTagIndex;
|
|
19
|
+
touch(event: NostrEvent): void;
|
|
20
|
+
hasEvent(uid: string): import("nostr-tools").Event | undefined;
|
|
21
|
+
getEvent(uid: string): import("nostr-tools").Event | undefined;
|
|
22
|
+
hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
|
|
23
|
+
getReplaceable(kind: number, pubkey: string, d?: string): import("nostr-tools").Event | undefined;
|
|
24
|
+
addEvent(event: NostrEvent): import("nostr-tools").Event;
|
|
25
|
+
deleteEvent(eventOrId: string | NostrEvent): boolean;
|
|
26
|
+
iterateAuthors(authors: Iterable<string>): Generator<import("nostr-tools").Event, void, unknown>;
|
|
27
|
+
iterateTag(tag: string, values: Iterable<string>): Generator<import("nostr-tools").Event, void, unknown>;
|
|
28
|
+
iterateKinds(kinds: Iterable<number>): Generator<import("nostr-tools").Event, void, unknown>;
|
|
29
|
+
iterateTime(since: number | undefined, until: number | undefined): Generator<never, Set<import("nostr-tools").Event>, unknown>;
|
|
30
|
+
iterateIds(ids: Iterable<string>): Generator<import("nostr-tools").Event, void, unknown>;
|
|
31
|
+
getEventsForFilter(filter: Filter): Set<NostrEvent>;
|
|
32
|
+
getForFilters(filters: Filter[]): Set<import("nostr-tools").Event>;
|
|
33
|
+
prune(): number | undefined;
|
|
34
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { LRU } from "tiny-lru";
|
|
2
|
+
import { getEventUID, getIndexableTags, getReplaceableUID } from "../helpers/event.js";
|
|
3
|
+
import { INDEXABLE_TAGS } from "./common.js";
|
|
4
|
+
import { logger } from "../logger.js";
|
|
5
|
+
import { binarySearch, insertEventIntoDescendingList } from "nostr-tools/utils";
|
|
6
|
+
export class Database {
|
|
7
|
+
log = logger.extend("Database");
|
|
8
|
+
/** Max number of events to hold */
|
|
9
|
+
max;
|
|
10
|
+
/** Indexes */
|
|
11
|
+
kinds = new Map();
|
|
12
|
+
authors = new Map();
|
|
13
|
+
tags = new LRU();
|
|
14
|
+
created_at = [];
|
|
15
|
+
/** LRU cache of last events touched */
|
|
16
|
+
events = new LRU();
|
|
17
|
+
constructor(max) {
|
|
18
|
+
this.max = max;
|
|
19
|
+
}
|
|
20
|
+
/** Index helper methods */
|
|
21
|
+
getKindIndex(kind) {
|
|
22
|
+
if (!this.kinds.has(kind))
|
|
23
|
+
this.kinds.set(kind, new Set());
|
|
24
|
+
return this.kinds.get(kind);
|
|
25
|
+
}
|
|
26
|
+
getAuthorsIndex(author) {
|
|
27
|
+
if (!this.authors.has(author))
|
|
28
|
+
this.authors.set(author, new Set());
|
|
29
|
+
return this.authors.get(author);
|
|
30
|
+
}
|
|
31
|
+
getTagIndex(tagAndValue) {
|
|
32
|
+
if (!this.tags.has(tagAndValue)) {
|
|
33
|
+
// build new tag index from existing events
|
|
34
|
+
const events = new Set();
|
|
35
|
+
const ts = Date.now();
|
|
36
|
+
for (const event of this.events.values()) {
|
|
37
|
+
if (getIndexableTags(event).has(tagAndValue)) {
|
|
38
|
+
events.add(event);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const took = Date.now() - ts;
|
|
42
|
+
if (took > 100)
|
|
43
|
+
this.log(`Built index ${tagAndValue} took ${took}ms`);
|
|
44
|
+
this.tags.set(tagAndValue, events);
|
|
45
|
+
}
|
|
46
|
+
return this.tags.get(tagAndValue);
|
|
47
|
+
}
|
|
48
|
+
touch(event) {
|
|
49
|
+
this.events.set(getEventUID(event), event);
|
|
50
|
+
}
|
|
51
|
+
hasEvent(uid) {
|
|
52
|
+
return this.events.get(uid);
|
|
53
|
+
}
|
|
54
|
+
getEvent(uid) {
|
|
55
|
+
return this.events.get(uid);
|
|
56
|
+
}
|
|
57
|
+
hasReplaceable(kind, pubkey, d) {
|
|
58
|
+
return this.events.has(getReplaceableUID(kind, pubkey, d));
|
|
59
|
+
}
|
|
60
|
+
getReplaceable(kind, pubkey, d) {
|
|
61
|
+
return this.events.get(getReplaceableUID(kind, pubkey, d));
|
|
62
|
+
}
|
|
63
|
+
addEvent(event) {
|
|
64
|
+
const uid = getEventUID(event);
|
|
65
|
+
const current = this.events.get(uid);
|
|
66
|
+
if (current && event.created_at <= current.created_at)
|
|
67
|
+
return current;
|
|
68
|
+
this.events.set(uid, event);
|
|
69
|
+
this.getKindIndex(event.kind).add(event);
|
|
70
|
+
this.getAuthorsIndex(event.pubkey).add(event);
|
|
71
|
+
for (const tag of getIndexableTags(event)) {
|
|
72
|
+
if (this.tags.has(tag)) {
|
|
73
|
+
this.getTagIndex(tag).add(event);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
insertEventIntoDescendingList(this.created_at, event);
|
|
77
|
+
return event;
|
|
78
|
+
}
|
|
79
|
+
deleteEvent(eventOrId) {
|
|
80
|
+
let event = typeof eventOrId === "string" ? this.events.get(eventOrId) : eventOrId;
|
|
81
|
+
if (!event)
|
|
82
|
+
throw new Error("Missing event");
|
|
83
|
+
const uid = getEventUID(event);
|
|
84
|
+
// only remove events that are known
|
|
85
|
+
if (!this.events.has(uid))
|
|
86
|
+
return false;
|
|
87
|
+
this.getAuthorsIndex(event.pubkey).delete(event);
|
|
88
|
+
this.getKindIndex(event.kind).delete(event);
|
|
89
|
+
for (const tag of getIndexableTags(event)) {
|
|
90
|
+
if (this.tags.has(tag)) {
|
|
91
|
+
this.getTagIndex(tag).delete(event);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// remove from created_at index
|
|
95
|
+
const i = this.created_at.indexOf(event);
|
|
96
|
+
this.created_at.splice(i, 1);
|
|
97
|
+
this.events.delete(uid);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
*iterateAuthors(authors) {
|
|
101
|
+
for (const author of authors) {
|
|
102
|
+
const events = this.authors.get(author);
|
|
103
|
+
if (events) {
|
|
104
|
+
for (const event of events)
|
|
105
|
+
yield event;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
*iterateTag(tag, values) {
|
|
110
|
+
for (const value of values) {
|
|
111
|
+
const events = this.getTagIndex(tag + ":" + value);
|
|
112
|
+
if (events) {
|
|
113
|
+
for (const event of events)
|
|
114
|
+
yield event;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
*iterateKinds(kinds) {
|
|
119
|
+
for (const kind of kinds) {
|
|
120
|
+
const events = this.kinds.get(kind);
|
|
121
|
+
if (events) {
|
|
122
|
+
for (const event of events)
|
|
123
|
+
yield event;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
*iterateTime(since, until) {
|
|
128
|
+
let untilIndex = 0;
|
|
129
|
+
let sinceIndex = this.created_at.length - 1;
|
|
130
|
+
let start = until
|
|
131
|
+
? binarySearch(this.created_at, (mid) => {
|
|
132
|
+
if (mid.created_at === until)
|
|
133
|
+
return -1;
|
|
134
|
+
return mid.created_at - until;
|
|
135
|
+
})
|
|
136
|
+
: undefined;
|
|
137
|
+
if (start && start[1])
|
|
138
|
+
untilIndex = start[0];
|
|
139
|
+
const end = since
|
|
140
|
+
? binarySearch(this.created_at, (mid) => {
|
|
141
|
+
if (mid.created_at === since)
|
|
142
|
+
return 1;
|
|
143
|
+
return since - mid.created_at;
|
|
144
|
+
})
|
|
145
|
+
: undefined;
|
|
146
|
+
if (end && end[1])
|
|
147
|
+
sinceIndex = end[0];
|
|
148
|
+
const events = new Set();
|
|
149
|
+
for (let i = untilIndex; i <= sinceIndex; i++) {
|
|
150
|
+
events.add(this.created_at[i]);
|
|
151
|
+
}
|
|
152
|
+
return events;
|
|
153
|
+
}
|
|
154
|
+
*iterateIds(ids) {
|
|
155
|
+
for (const id of ids) {
|
|
156
|
+
if (this.events.has(id))
|
|
157
|
+
yield this.events.get(id);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
getEventsForFilter(filter) {
|
|
161
|
+
// search is not supported, return an empty set
|
|
162
|
+
if (filter.search)
|
|
163
|
+
return new Set();
|
|
164
|
+
let first = true;
|
|
165
|
+
let events = new Set();
|
|
166
|
+
const and = (iterable) => {
|
|
167
|
+
const set = iterable instanceof Set ? iterable : new Set(iterable);
|
|
168
|
+
if (first) {
|
|
169
|
+
events = set;
|
|
170
|
+
first = false;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
for (const event of events) {
|
|
174
|
+
if (!set.has(event))
|
|
175
|
+
events.delete(event);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return events;
|
|
179
|
+
};
|
|
180
|
+
if (filter.ids)
|
|
181
|
+
and(this.iterateIds(filter.ids));
|
|
182
|
+
let time = null;
|
|
183
|
+
// query for time first if since is set
|
|
184
|
+
if (filter.since !== undefined) {
|
|
185
|
+
time = Array.from(this.iterateTime(filter.since, filter.until));
|
|
186
|
+
and(time);
|
|
187
|
+
}
|
|
188
|
+
for (const t of INDEXABLE_TAGS) {
|
|
189
|
+
const key = `#${t}`;
|
|
190
|
+
const values = filter[key];
|
|
191
|
+
if (values?.length)
|
|
192
|
+
and(this.iterateTag(t, values));
|
|
193
|
+
}
|
|
194
|
+
if (filter.authors)
|
|
195
|
+
and(this.iterateAuthors(filter.authors));
|
|
196
|
+
if (filter.kinds)
|
|
197
|
+
and(this.iterateKinds(filter.kinds));
|
|
198
|
+
// query for time last if only until is set
|
|
199
|
+
if (filter.since === undefined && filter.until !== undefined) {
|
|
200
|
+
time = Array.from(this.iterateTime(filter.since, filter.until));
|
|
201
|
+
and(time);
|
|
202
|
+
}
|
|
203
|
+
// if the filter queried on time and has a limit. truncate the events now
|
|
204
|
+
if (filter.limit && time) {
|
|
205
|
+
const limited = new Set();
|
|
206
|
+
for (const event of time) {
|
|
207
|
+
if (limited.size >= filter.limit)
|
|
208
|
+
break;
|
|
209
|
+
if (events.has(event))
|
|
210
|
+
limited.add(event);
|
|
211
|
+
}
|
|
212
|
+
return limited;
|
|
213
|
+
}
|
|
214
|
+
return events;
|
|
215
|
+
}
|
|
216
|
+
getForFilters(filters) {
|
|
217
|
+
if (filters.length === 0)
|
|
218
|
+
throw new Error("No Filters");
|
|
219
|
+
let events = new Set();
|
|
220
|
+
for (const filter of filters) {
|
|
221
|
+
const filtered = this.getEventsForFilter(filter);
|
|
222
|
+
for (const event of filtered)
|
|
223
|
+
events.add(event);
|
|
224
|
+
}
|
|
225
|
+
return events;
|
|
226
|
+
}
|
|
227
|
+
prune() {
|
|
228
|
+
if (!this.max)
|
|
229
|
+
return;
|
|
230
|
+
let removed = 0;
|
|
231
|
+
while (this.events.size > this.max) {
|
|
232
|
+
const event = this.events.first;
|
|
233
|
+
if (event) {
|
|
234
|
+
this.deleteEvent(event);
|
|
235
|
+
removed++;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return removed;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
+
import Observable from "zen-observable";
|
|
3
|
+
import { Database } from "./database.js";
|
|
4
|
+
export declare class EventStore {
|
|
5
|
+
events: Database;
|
|
6
|
+
private singles;
|
|
7
|
+
private streams;
|
|
8
|
+
constructor();
|
|
9
|
+
add(event: NostrEvent, fromRelay?: string): import("nostr-tools").Event;
|
|
10
|
+
getAll(filters: Filter[]): Set<import("nostr-tools").Event>;
|
|
11
|
+
hasEvent(uid: string): import("nostr-tools").Event | undefined;
|
|
12
|
+
getEvent(uid: string): import("nostr-tools").Event | undefined;
|
|
13
|
+
hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
|
|
14
|
+
getReplaceable(kind: number, pubkey: string, d?: string): import("nostr-tools").Event | undefined;
|
|
15
|
+
/** Creates an observable that updates a single event */
|
|
16
|
+
single(uid: string): Observable<import("nostr-tools").Event>;
|
|
17
|
+
/** Creates an observable that streams all events that match the filter */
|
|
18
|
+
stream(filters: Filter[]): Observable<import("nostr-tools").Event>;
|
|
19
|
+
/** Creates an observable that updates with an array of sorted events */
|
|
20
|
+
timeline(filters: Filter[]): Observable<import("nostr-tools").Event[]>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { insertEventIntoDescendingList } from "nostr-tools/utils";
|
|
2
|
+
import Observable from "zen-observable";
|
|
3
|
+
import { Database } from "./database.js";
|
|
4
|
+
import { getEventUID } from "../helpers/event.js";
|
|
5
|
+
import { matchFilters } from "../helpers/filter.js";
|
|
6
|
+
import { addSeenRelay } from "../helpers/relays.js";
|
|
7
|
+
export class EventStore {
|
|
8
|
+
events;
|
|
9
|
+
singles = new Map();
|
|
10
|
+
streams = new Map();
|
|
11
|
+
constructor() {
|
|
12
|
+
this.events = new Database();
|
|
13
|
+
}
|
|
14
|
+
add(event, fromRelay) {
|
|
15
|
+
const inserted = this.events.addEvent(event);
|
|
16
|
+
if (inserted === event) {
|
|
17
|
+
// forward to single event requests
|
|
18
|
+
const eventUID = getEventUID(event);
|
|
19
|
+
for (const [control, uid] of this.singles) {
|
|
20
|
+
if (eventUID === uid)
|
|
21
|
+
control.next(event);
|
|
22
|
+
}
|
|
23
|
+
// forward to streams
|
|
24
|
+
for (const [control, filters] of this.streams) {
|
|
25
|
+
if (matchFilters(filters, event))
|
|
26
|
+
control.next(event);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (fromRelay)
|
|
30
|
+
addSeenRelay(inserted, fromRelay);
|
|
31
|
+
return inserted;
|
|
32
|
+
}
|
|
33
|
+
getAll(filters) {
|
|
34
|
+
return this.events.getForFilters(filters);
|
|
35
|
+
}
|
|
36
|
+
hasEvent(uid) {
|
|
37
|
+
return this.events.hasEvent(uid);
|
|
38
|
+
}
|
|
39
|
+
getEvent(uid) {
|
|
40
|
+
return this.events.getEvent(uid);
|
|
41
|
+
}
|
|
42
|
+
hasReplaceable(kind, pubkey, d) {
|
|
43
|
+
return this.events.hasReplaceable(kind, pubkey, d);
|
|
44
|
+
}
|
|
45
|
+
getReplaceable(kind, pubkey, d) {
|
|
46
|
+
return this.events.getReplaceable(kind, pubkey, d);
|
|
47
|
+
}
|
|
48
|
+
/** Creates an observable that updates a single event */
|
|
49
|
+
single(uid) {
|
|
50
|
+
return new Observable((control) => {
|
|
51
|
+
const event = this.events.getEvent(uid);
|
|
52
|
+
if (event)
|
|
53
|
+
control.next(event);
|
|
54
|
+
this.singles.set(control, uid);
|
|
55
|
+
return () => {
|
|
56
|
+
this.singles.delete(control);
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** Creates an observable that streams all events that match the filter */
|
|
61
|
+
stream(filters) {
|
|
62
|
+
return new Observable((control) => {
|
|
63
|
+
const events = this.events.getForFilters(filters);
|
|
64
|
+
for (const event of events)
|
|
65
|
+
control.next(event);
|
|
66
|
+
this.streams.set(control, filters);
|
|
67
|
+
return () => {
|
|
68
|
+
this.streams.delete(control);
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Creates an observable that updates with an array of sorted events */
|
|
73
|
+
timeline(filters) {
|
|
74
|
+
let events = [];
|
|
75
|
+
return this.stream(filters).map((event) => {
|
|
76
|
+
insertEventIntoDescendingList(events, event);
|
|
77
|
+
return events;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './event-store.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './event-store.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare function isReplaceable(kind: number): boolean;
|
|
3
|
+
/** returns the events Unique ID */
|
|
4
|
+
export declare function getEventUID(event: NostrEvent): string;
|
|
5
|
+
export declare function getReplaceableUID(kind: number, pubkey: string, d?: string): string;
|
|
6
|
+
export declare function getIndexableTags(event: NostrEvent): Set<string>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { INDEXABLE_TAGS } from "../event-store/common.js";
|
|
3
|
+
import { EventIndexableTags, EventUID } from "./symbols.js";
|
|
4
|
+
export function isReplaceable(kind) {
|
|
5
|
+
return kinds.isReplaceableKind(kind) || kinds.isParameterizedReplaceableKind(kind);
|
|
6
|
+
}
|
|
7
|
+
/** returns the events Unique ID */
|
|
8
|
+
export function getEventUID(event) {
|
|
9
|
+
if (!event[EventUID]) {
|
|
10
|
+
if (isReplaceable(event.kind)) {
|
|
11
|
+
const d = event.tags.find((t) => t[0] === "d")?.[1];
|
|
12
|
+
event[EventUID] = getReplaceableUID(event.kind, event.pubkey, d);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
event[EventUID] = event.id;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return event[EventUID];
|
|
19
|
+
}
|
|
20
|
+
export function getReplaceableUID(kind, pubkey, d) {
|
|
21
|
+
return d ? `${kind}:${pubkey}:${d}` : `${kind}:${pubkey}`;
|
|
22
|
+
}
|
|
23
|
+
export function getIndexableTags(event) {
|
|
24
|
+
if (!event[EventIndexableTags]) {
|
|
25
|
+
const tags = new Set();
|
|
26
|
+
for (const tag of event.tags) {
|
|
27
|
+
if (tag[0] && INDEXABLE_TAGS.has(tag[0]) && tag[1]) {
|
|
28
|
+
tags.add(tag[0] + ":" + tag[1]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
event[EventIndexableTags] = tags;
|
|
32
|
+
}
|
|
33
|
+
return event[EventIndexableTags];
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getIndexableTags } from "./event.js";
|
|
2
|
+
// Copied from https://github.com/nbd-wtf/nostr-tools/blob/a61cde77eacc9518001f11d7f67f1a50ae05fd80/filter.ts
|
|
3
|
+
// And modified to use getIndexableTags
|
|
4
|
+
export function matchFilter(filter, event) {
|
|
5
|
+
if (filter.ids && filter.ids.indexOf(event.id) === -1) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (filter.kinds && filter.kinds.indexOf(event.kind) === -1) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (filter.authors && filter.authors.indexOf(event.pubkey) === -1) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
for (let f in filter) {
|
|
15
|
+
if (f[0] === "#") {
|
|
16
|
+
let tagName = f.slice(1);
|
|
17
|
+
let values = filter[`#${tagName}`];
|
|
18
|
+
if (values) {
|
|
19
|
+
const tags = getIndexableTags(event);
|
|
20
|
+
if (values.some((v) => !tags.has(tagName + ":" + v)))
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (filter.since && event.created_at < filter.since)
|
|
26
|
+
return false;
|
|
27
|
+
if (filter.until && event.created_at > filter.until)
|
|
28
|
+
return false;
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
export function matchFilters(filters, event) {
|
|
32
|
+
for (let i = 0; i < filters.length; i++) {
|
|
33
|
+
if (matchFilter(filters[i], event)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { safeRelayUrl } from "./relays.js";
|
|
2
|
+
import { MailboxesInboxes, MailboxesOutboxes } from "./symbols.js";
|
|
3
|
+
export function getInboxes(event) {
|
|
4
|
+
if (!event[MailboxesInboxes]) {
|
|
5
|
+
const inboxes = new Set();
|
|
6
|
+
for (const tag of event.tags) {
|
|
7
|
+
if (tag[0] === "r" && tag[1] && (tag[2] === "read" || tag[2] === undefined)) {
|
|
8
|
+
const url = safeRelayUrl(tag[1]);
|
|
9
|
+
if (url)
|
|
10
|
+
inboxes.add(url);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
event[MailboxesInboxes] = inboxes;
|
|
14
|
+
}
|
|
15
|
+
return event[MailboxesInboxes];
|
|
16
|
+
}
|
|
17
|
+
export function getOutboxes(event) {
|
|
18
|
+
if (!event[MailboxesOutboxes]) {
|
|
19
|
+
const outboxes = new Set();
|
|
20
|
+
for (const tag of event.tags) {
|
|
21
|
+
if (tag[0] === "r" && tag[1] && (tag[2] === "write" || tag[2] === undefined)) {
|
|
22
|
+
const url = safeRelayUrl(tag[1]);
|
|
23
|
+
if (url)
|
|
24
|
+
outboxes.add(url);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
event[MailboxesOutboxes] = outboxes;
|
|
28
|
+
}
|
|
29
|
+
return event[MailboxesOutboxes];
|
|
30
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export type ProfileContent = {
|
|
3
|
+
name?: string;
|
|
4
|
+
display_name?: string;
|
|
5
|
+
displayName?: string;
|
|
6
|
+
about?: string;
|
|
7
|
+
/** @deprecated */
|
|
8
|
+
image?: string;
|
|
9
|
+
picture?: string;
|
|
10
|
+
banner?: string;
|
|
11
|
+
website?: string;
|
|
12
|
+
lud16?: string;
|
|
13
|
+
lud06?: string;
|
|
14
|
+
nip05?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function getProfileContent(event: NostrEvent): ProfileContent;
|
|
17
|
+
export declare function getProfileContent(event: NostrEvent, quite: false): ProfileContent;
|
|
18
|
+
export declare function getProfileContent(event: NostrEvent, quite: true): ProfileContent | Error;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ProfileContent } from "./symbols.js";
|
|
2
|
+
export function getProfileContent(event, quite = false) {
|
|
3
|
+
let cached = event[ProfileContent];
|
|
4
|
+
if (!cached) {
|
|
5
|
+
try {
|
|
6
|
+
const profile = JSON.parse(event.content);
|
|
7
|
+
// ensure nip05 is a string
|
|
8
|
+
if (profile.nip05 && typeof profile.nip05 !== "string")
|
|
9
|
+
profile.nip05 = String(profile.nip05);
|
|
10
|
+
cached = event[ProfileContent] = profile;
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
if (e instanceof Error)
|
|
14
|
+
cached = event[ProfileContent] = e;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (cached === undefined) {
|
|
18
|
+
throw new Error("Failed to parse profile");
|
|
19
|
+
}
|
|
20
|
+
else if (cached instanceof Error) {
|
|
21
|
+
if (!quite)
|
|
22
|
+
throw cached;
|
|
23
|
+
else
|
|
24
|
+
return cached;
|
|
25
|
+
}
|
|
26
|
+
else
|
|
27
|
+
return cached;
|
|
28
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare function addSeenRelay(event: NostrEvent, relay: string): Set<string>;
|
|
3
|
+
export declare function getSeenRelays(event: NostrEvent): Set<string> | undefined;
|
|
4
|
+
export declare function validateRelayURL(relay: string | URL): URL;
|
|
5
|
+
export declare function safeRelayUrl(relayUrl: string | URL): string | null;
|
|
6
|
+
export declare function safeRelayUrls(urls: Iterable<string>): string[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FromRelays } from "./symbols.js";
|
|
2
|
+
export function addSeenRelay(event, relay) {
|
|
3
|
+
if (!event[FromRelays])
|
|
4
|
+
event[FromRelays] = new Set();
|
|
5
|
+
event[FromRelays].add(relay);
|
|
6
|
+
return event[FromRelays];
|
|
7
|
+
}
|
|
8
|
+
export function getSeenRelays(event) {
|
|
9
|
+
return event[FromRelays];
|
|
10
|
+
}
|
|
11
|
+
// Relay URLs
|
|
12
|
+
export function validateRelayURL(relay) {
|
|
13
|
+
if (typeof relay === "string" && relay.includes(",ws"))
|
|
14
|
+
throw new Error("Can not have multiple relays in one string");
|
|
15
|
+
const url = typeof relay === "string" ? new URL(relay) : relay;
|
|
16
|
+
if (url.protocol !== "wss:" && url.protocol !== "ws:")
|
|
17
|
+
throw new Error("Incorrect protocol");
|
|
18
|
+
return url;
|
|
19
|
+
}
|
|
20
|
+
export function safeRelayUrl(relayUrl) {
|
|
21
|
+
try {
|
|
22
|
+
return validateRelayURL(relayUrl).toString();
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function safeRelayUrls(urls) {
|
|
29
|
+
return Array.from(urls).map(safeRelayUrl).filter(Boolean);
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ProfileContent as TProfileContent } from "./profile.js";
|
|
2
|
+
export declare const EventUID: unique symbol;
|
|
3
|
+
export declare const EventIndexableTags: unique symbol;
|
|
4
|
+
export declare const MailboxesInboxes: unique symbol;
|
|
5
|
+
export declare const MailboxesOutboxes: unique symbol;
|
|
6
|
+
export declare const ProfileContent: unique symbol;
|
|
7
|
+
export declare const FromRelays: unique symbol;
|
|
8
|
+
declare module "nostr-tools" {
|
|
9
|
+
interface Event {
|
|
10
|
+
[EventUID]?: string;
|
|
11
|
+
[MailboxesInboxes]?: Set<string>;
|
|
12
|
+
[MailboxesOutboxes]?: Set<string>;
|
|
13
|
+
[ProfileContent]?: TProfileContent | Error;
|
|
14
|
+
[EventIndexableTags]?: Set<string>;
|
|
15
|
+
[FromRelays]?: Set<string>;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// event
|
|
2
|
+
export const EventUID = Symbol.for("event-uid");
|
|
3
|
+
export const EventIndexableTags = Symbol.for("indexable-tags");
|
|
4
|
+
// mailboxes
|
|
5
|
+
export const MailboxesInboxes = Symbol.for("mailboxes-inboxes");
|
|
6
|
+
export const MailboxesOutboxes = Symbol.for("mailboxes-outboxes");
|
|
7
|
+
// profile
|
|
8
|
+
export const ProfileContent = Symbol.for("profile-content");
|
|
9
|
+
// event relays
|
|
10
|
+
export const FromRelays = Symbol.for("from-relays");
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Observable from "zen-observable";
|
|
2
|
+
export type StatefulObservable<T> = Observable<T> & {
|
|
3
|
+
_stateful?: true;
|
|
4
|
+
value?: T;
|
|
5
|
+
error?: Error;
|
|
6
|
+
complete?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/** Wraps an observable and makes it stateful */
|
|
9
|
+
export declare function stateful<T extends unknown>(observable: Observable<T>, cleanup?: boolean): StatefulObservable<T>;
|
|
10
|
+
export declare function isStateful<T extends unknown>(observable: Observable<T> | StatefulObservable<T>): observable is StatefulObservable<T>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Observable from "zen-observable";
|
|
2
|
+
/** Wraps an observable and makes it stateful */
|
|
3
|
+
export function stateful(observable, cleanup = false) {
|
|
4
|
+
let subscription = undefined;
|
|
5
|
+
let observers = [];
|
|
6
|
+
const self = new Observable((observer) => {
|
|
7
|
+
// add observer to list
|
|
8
|
+
observers.push(observer);
|
|
9
|
+
// pass any cached values
|
|
10
|
+
if (self.value)
|
|
11
|
+
observer.next(self.value);
|
|
12
|
+
if (self.error)
|
|
13
|
+
observer.error(self.error);
|
|
14
|
+
if (self.complete)
|
|
15
|
+
observer.complete();
|
|
16
|
+
// subscribe if not already
|
|
17
|
+
if (!subscription) {
|
|
18
|
+
subscription = observable.subscribe({
|
|
19
|
+
next: (v) => {
|
|
20
|
+
self.value = v;
|
|
21
|
+
for (const observer of observers)
|
|
22
|
+
observer.next(v);
|
|
23
|
+
},
|
|
24
|
+
error: (err) => {
|
|
25
|
+
self.error = err;
|
|
26
|
+
for (const observer of observers)
|
|
27
|
+
observer.error(err);
|
|
28
|
+
},
|
|
29
|
+
complete: () => {
|
|
30
|
+
self.complete = true;
|
|
31
|
+
for (const observer of observers)
|
|
32
|
+
observer.complete();
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return () => {
|
|
37
|
+
let i = observers.indexOf(observer);
|
|
38
|
+
if (i !== -1) {
|
|
39
|
+
// remove observer from list
|
|
40
|
+
observers.splice(i, 1);
|
|
41
|
+
if (subscription && observers.length === 0) {
|
|
42
|
+
subscription.unsubscribe();
|
|
43
|
+
subscription = undefined;
|
|
44
|
+
// reset cached values
|
|
45
|
+
if (cleanup) {
|
|
46
|
+
delete self.value;
|
|
47
|
+
delete self.error;
|
|
48
|
+
delete self.complete;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
self._stateful = true;
|
|
55
|
+
return self;
|
|
56
|
+
}
|
|
57
|
+
export function isStateful(observable) {
|
|
58
|
+
// @ts-expect-error
|
|
59
|
+
return observable._stateful;
|
|
60
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Observable from "zen-observable";
|
|
2
|
+
export function throttle(source, interval) {
|
|
3
|
+
return new Observable((observer) => {
|
|
4
|
+
let lastEmissionTime = 0;
|
|
5
|
+
let subscription = source.subscribe({
|
|
6
|
+
next(value) {
|
|
7
|
+
const currentTime = Date.now();
|
|
8
|
+
if (currentTime - lastEmissionTime >= interval) {
|
|
9
|
+
lastEmissionTime = currentTime;
|
|
10
|
+
observer.next(value);
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
error(err) {
|
|
14
|
+
observer.error(err);
|
|
15
|
+
},
|
|
16
|
+
complete() {
|
|
17
|
+
observer.complete();
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
return () => subscription.unsubscribe();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default function createDefer() {
|
|
2
|
+
let _resolve;
|
|
3
|
+
let _reject;
|
|
4
|
+
const promise = new Promise((resolve, reject) => {
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
_resolve = resolve;
|
|
7
|
+
_reject = reject;
|
|
8
|
+
});
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
promise.resolve = _resolve;
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
promise.reject = _reject;
|
|
13
|
+
return promise;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './deferred.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './deferred.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Observable from "zen-observable";
|
|
2
|
+
import { LRU } from "tiny-lru";
|
|
3
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
4
|
+
import { EventStore } from "../event-store/event-store.js";
|
|
5
|
+
import { ProfileContent } from "../helpers/profile.js";
|
|
6
|
+
export declare class QueryStore {
|
|
7
|
+
store: EventStore;
|
|
8
|
+
constructor(store: EventStore);
|
|
9
|
+
singleEvents: LRU<Observable<import("nostr-tools").Event>>;
|
|
10
|
+
getEvent(id: string): Observable<import("nostr-tools").Event>;
|
|
11
|
+
getReplaceable(kind: number, pubkey: string, d?: string): Observable<import("nostr-tools").Event>;
|
|
12
|
+
timelines: LRU<Observable<import("nostr-tools").Event[]>>;
|
|
13
|
+
getTimeline(filters: Filter[]): Observable<import("nostr-tools").Event[]>;
|
|
14
|
+
profiles: LRU<Observable<ProfileContent>>;
|
|
15
|
+
getProfile(pubkey: string): Observable<ProfileContent>;
|
|
16
|
+
reactions: LRU<Observable<import("nostr-tools").Event[]>>;
|
|
17
|
+
getReactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
|
|
18
|
+
mailboxes: LRU<Observable<{
|
|
19
|
+
inboxes: Set<string>;
|
|
20
|
+
outboxes: Set<string>;
|
|
21
|
+
}>>;
|
|
22
|
+
getMailboxes(pubkey: string): Observable<{
|
|
23
|
+
inboxes: Set<string>;
|
|
24
|
+
outboxes: Set<string>;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { LRU } from "tiny-lru";
|
|
2
|
+
import { kinds } from "nostr-tools";
|
|
3
|
+
import stringify from "json-stringify-deterministic";
|
|
4
|
+
import { stateful } from "../observable/stateful.js";
|
|
5
|
+
import { getProfileContent } from "../helpers/profile.js";
|
|
6
|
+
import { getEventUID, getReplaceableUID, isReplaceable } from "../helpers/event.js";
|
|
7
|
+
import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
|
|
8
|
+
export class QueryStore {
|
|
9
|
+
store;
|
|
10
|
+
constructor(store) {
|
|
11
|
+
this.store = store;
|
|
12
|
+
}
|
|
13
|
+
singleEvents = new LRU();
|
|
14
|
+
getEvent(id) {
|
|
15
|
+
if (!this.singleEvents.has(id)) {
|
|
16
|
+
const observable = stateful(this.store.single(id));
|
|
17
|
+
this.singleEvents.set(id, observable);
|
|
18
|
+
}
|
|
19
|
+
return this.singleEvents.get(id);
|
|
20
|
+
}
|
|
21
|
+
getReplaceable(kind, pubkey, d) {
|
|
22
|
+
return this.getEvent(getReplaceableUID(kind, pubkey, d));
|
|
23
|
+
}
|
|
24
|
+
timelines = new LRU();
|
|
25
|
+
getTimeline(filters) {
|
|
26
|
+
const key = stringify(filters);
|
|
27
|
+
if (!this.singleEvents.has(key)) {
|
|
28
|
+
const observable = stateful(this.store.timeline(filters));
|
|
29
|
+
this.timelines.set(key, observable);
|
|
30
|
+
}
|
|
31
|
+
return this.timelines.get(key);
|
|
32
|
+
}
|
|
33
|
+
profiles = new LRU();
|
|
34
|
+
getProfile(pubkey) {
|
|
35
|
+
if (!this.profiles.has(pubkey)) {
|
|
36
|
+
const observable = stateful(this.getReplaceable(kinds.Metadata, pubkey).map((event) => getProfileContent(event)));
|
|
37
|
+
this.profiles.set(pubkey, observable);
|
|
38
|
+
}
|
|
39
|
+
return this.profiles.get(pubkey);
|
|
40
|
+
}
|
|
41
|
+
reactions = new LRU();
|
|
42
|
+
getReactions(event) {
|
|
43
|
+
const uid = getEventUID(event);
|
|
44
|
+
if (!this.reactions.has(uid)) {
|
|
45
|
+
const observable = this.getTimeline(isReplaceable(event.kind)
|
|
46
|
+
? [
|
|
47
|
+
{ kinds: [kinds.Reaction], "#e": [event.id] },
|
|
48
|
+
{ kinds: [kinds.Reaction], "#a": [uid] },
|
|
49
|
+
]
|
|
50
|
+
: [
|
|
51
|
+
{
|
|
52
|
+
kinds: [kinds.Reaction],
|
|
53
|
+
"#e": [event.id],
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
this.reactions.set(uid, observable);
|
|
57
|
+
}
|
|
58
|
+
return this.reactions.get(uid);
|
|
59
|
+
}
|
|
60
|
+
mailboxes = new LRU();
|
|
61
|
+
getMailboxes(pubkey) {
|
|
62
|
+
if (!this.mailboxes.has(pubkey)) {
|
|
63
|
+
const observable = stateful(this.getReplaceable(kinds.RelayList, pubkey).map((event) => ({
|
|
64
|
+
inboxes: getInboxes(event),
|
|
65
|
+
outboxes: getOutboxes(event),
|
|
66
|
+
})));
|
|
67
|
+
this.mailboxes.set(pubkey, observable);
|
|
68
|
+
}
|
|
69
|
+
return this.mailboxes.get(pubkey);
|
|
70
|
+
}
|
|
71
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "applesauce-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"nostr"
|
|
10
|
+
],
|
|
11
|
+
"author": "hzrd149",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./helpers": {
|
|
22
|
+
"import": "./dist/helpers/index.js",
|
|
23
|
+
"types": "./dist/helpers/index.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./observable": {
|
|
26
|
+
"import": "./dist/observable/index.js",
|
|
27
|
+
"types": "./dist/observable/index.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./query-store": {
|
|
30
|
+
"import": "./dist/query-store/index.js",
|
|
31
|
+
"types": "./dist/query-store/index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./event-store": {
|
|
34
|
+
"import": "./dist/event-store/index.js",
|
|
35
|
+
"types": "./dist/event-store/index.d.ts"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"debug": "^4.3.7",
|
|
40
|
+
"json-stringify-deterministic": "^1.0.12",
|
|
41
|
+
"nanoid": "^5.0.7",
|
|
42
|
+
"nostr-tools": "^2.7.2",
|
|
43
|
+
"tiny-lru": "^11.2.11"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/debug": "^4.1.12",
|
|
47
|
+
"@types/zen-observable": "^0.8.7",
|
|
48
|
+
"zen-observable": "^0.10.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc"
|
|
52
|
+
}
|
|
53
|
+
}
|