@qntx/nostr 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abort-Ba8tDrOI.mjs +38 -0
- package/dist/abort-Ba8tDrOI.mjs.map +1 -0
- package/dist/{builder-BphJa-Y-.d.mts → builder-BWn55Paf.d.mts} +20 -14
- package/dist/{builder-q0VsvnYE.mjs → builder-CyrGKRVP.mjs} +32 -16
- package/dist/builder-CyrGKRVP.mjs.map +1 -0
- package/dist/{client-Bd8B1mYm.mjs → client-BWsX9Cv1.mjs} +354 -195
- package/dist/client-BWsX9Cv1.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/core.d.mts +8 -8
- package/dist/core.mjs +8 -9
- package/dist/error-BW7re1UN.d.mts +9 -0
- package/dist/error-B__OMBRY.mjs +14 -0
- package/dist/error-B__OMBRY.mjs.map +1 -0
- package/dist/{error-WEFS-R-4.d.mts → error-CR8Urc84.d.mts} +20 -8
- package/dist/{error-CwBxle6N.mjs → error-DrEIM7FO.mjs} +5 -3
- package/dist/error-DrEIM7FO.mjs.map +1 -0
- package/dist/{event-CzoQEFMF.mjs → event-D8Nfoh4K.mjs} +40 -32
- package/dist/event-D8Nfoh4K.mjs.map +1 -0
- package/dist/{event-B5D2jxt_.d.mts → event-fU3Y01PR.d.mts} +23 -20
- package/dist/{filter-BxwjEkZp.mjs → filter-BQl3GlHt.mjs} +42 -35
- package/dist/filter-BQl3GlHt.mjs.map +1 -0
- package/dist/filter-C0IMByE3.d.mts +37 -0
- package/dist/{gossip-C5RVB1UB.d.mts → gossip-BRQwIM_q.d.mts} +25 -23
- package/dist/{gossip-3xkRRCm_.mjs → gossip-oloDMWEj.mjs} +52 -34
- package/dist/gossip-oloDMWEj.mjs.map +1 -0
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/{http-6vevbBlu.d.mts → http-C4HwIu4B.d.mts} +7 -7
- package/dist/{http-D2rhokZo.mjs → http-CuazNRIP.mjs} +10 -6
- package/dist/http-CuazNRIP.mjs.map +1 -0
- package/dist/index-B425CAcQ.d.mts +328 -0
- package/dist/index-BVyfUBuG.d.mts +181 -0
- package/dist/{index-BYMnhRIC.d.mts → index-Ba7bJ7DD.d.mts} +68 -50
- package/dist/{index-DaRyjo7O.d.mts → index-Boe8FshM.d.mts} +13 -7
- package/dist/index-CUIvFQdE.d.mts +73 -0
- package/dist/index-JdaS_uce.d.mts +207 -0
- package/dist/index.d.mts +22 -20
- package/dist/index.mjs +19 -18
- package/dist/{key-Dn-aX4x4.mjs → key-Bbtys6o7.mjs} +22 -14
- package/dist/key-Bbtys6o7.mjs.map +1 -0
- package/dist/{key-BiGxGLhg.d.mts → key-BlsYhN1V.d.mts} +9 -6
- package/dist/{kind-BLl4cLOc.mjs → kind-Cxwhhkx8.mjs} +2 -1
- package/dist/kind-Cxwhhkx8.mjs.map +1 -0
- package/dist/{loaders-BNQszDAn.mjs → loaders-o6YXB_Ri.mjs} +329 -305
- package/dist/loaders-o6YXB_Ri.mjs.map +1 -0
- package/dist/loaders.d.mts +3 -3
- package/dist/loaders.mjs +2 -2
- package/dist/memory-C2EVx0_O.mjs +46 -0
- package/dist/memory-C2EVx0_O.mjs.map +1 -0
- package/dist/{memory-BCmhZPek.mjs → memory-index-Do1mz0WF.mjs} +212 -82
- package/dist/memory-index-Do1mz0WF.mjs.map +1 -0
- package/dist/{message-C_Uj-Y-c.mjs → message-DcY6Fi9o.mjs} +38 -24
- package/dist/message-DcY6Fi9o.mjs.map +1 -0
- package/dist/{message-DJC-Jz6T.d.mts → message-iGPIqnn0.d.mts} +15 -8
- package/dist/{nip17-BWq-Kfq5.d.mts → nip17-DNEkz8rM.d.mts} +18 -17
- package/dist/{nip19-D7905oIv.d.mts → nip19-BevCJZsp.d.mts} +23 -3
- package/dist/{nip46-C_DoxiS_.d.mts → nip46-DDV4dC56.d.mts} +13 -12
- package/dist/{nip59-DljW_sYh.d.mts → nip59-zQgdLcEr.d.mts} +28 -25
- package/dist/{nip65-Di5hZRuV.d.mts → nip65-DBfh2smt.d.mts} +8 -3
- package/dist/{nip77-ByxlgoQO.d.mts → nip77-DH4RhS5r.d.mts} +9 -8
- package/dist/nips/blossom.d.mts +37 -37
- package/dist/nips/blossom.mjs +67 -52
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.d.mts +2 -3
- package/dist/nips/nip04.mjs +6 -10
- package/dist/nips/nip04.mjs.map +1 -1
- package/dist/nips/nip05.d.mts +37 -43
- package/dist/nips/nip05.mjs +57 -57
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +18 -19
- package/dist/nips/nip10.mjs +76 -61
- package/dist/nips/nip10.mjs.map +1 -1
- package/dist/nips/nip11.d.mts +9 -8
- package/dist/nips/nip11.mjs +14 -11
- package/dist/nips/nip11.mjs.map +1 -1
- package/dist/nips/nip13.d.mts +9 -9
- package/dist/nips/nip13.mjs +15 -17
- package/dist/nips/nip13.mjs.map +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +17 -12
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +1 -1
- package/dist/nips/nip19.mjs +52 -29
- package/dist/nips/nip19.mjs.map +1 -1
- package/dist/nips/nip21.d.mts +5 -6
- package/dist/nips/nip21.mjs +8 -7
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +7 -8
- package/dist/nips/nip27.mjs +26 -25
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.d.mts +5 -6
- package/dist/nips/nip42.mjs +3 -3
- package/dist/nips/nip42.mjs.map +1 -1
- package/dist/nips/nip44.d.mts +24 -7
- package/dist/nips/nip44.mjs +25 -15
- package/dist/nips/nip44.mjs.map +1 -1
- package/dist/nips/nip46.d.mts +1 -1
- package/dist/nips/nip46.mjs +52 -49
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +8 -7
- package/dist/nips/nip49.mjs +16 -12
- package/dist/nips/nip49.mjs.map +1 -1
- package/dist/nips/nip51.d.mts +25 -27
- package/dist/nips/nip51.mjs +47 -36
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.d.mts +12 -13
- package/dist/nips/nip57.mjs +35 -38
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +14 -18
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +11 -8
- package/dist/nips/nip65.mjs.map +1 -1
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +56 -34
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip96.d.mts +21 -12
- package/dist/nips/nip96.mjs +59 -36
- package/dist/nips/nip96.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +12 -12
- package/dist/nips/nip98.mjs +18 -19
- package/dist/nips/nip98.mjs.map +1 -1
- package/dist/{pool-C_j1OmBB.mjs → pool-XmAGaufS.mjs} +611 -487
- package/dist/pool-XmAGaufS.mjs.map +1 -0
- package/dist/pool-tEr7c5bG.d.mts +302 -0
- package/dist/reactive-DDlThDSZ.mjs +324 -0
- package/dist/reactive-DDlThDSZ.mjs.map +1 -0
- package/dist/reactive-DFpqtIf_.d.mts +66 -0
- package/dist/relay.d.mts +5 -4
- package/dist/relay.mjs +2 -2
- package/dist/report-CHld4uWj.mjs +32 -0
- package/dist/report-CHld4uWj.mjs.map +1 -0
- package/dist/{signer-CZ6K6FUc.mjs → signer-CKR9EHce.mjs} +107 -72
- package/dist/signer-CKR9EHce.mjs.map +1 -0
- package/dist/signer.d.mts +3 -3
- package/dist/signer.mjs +3 -2
- package/dist/storage-xXxLGCVb.mjs +1376 -0
- package/dist/storage-xXxLGCVb.mjs.map +1 -0
- package/dist/storage.d.mts +4 -4
- package/dist/storage.mjs +5 -4
- package/dist/store.d.mts +2 -0
- package/dist/store.mjs +2 -0
- package/dist/testing.d.mts +125 -0
- package/dist/testing.mjs +957 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-2pCUprRU.d.mts +47 -0
- package/dist/types-CRQEk0Oi.d.mts +19 -0
- package/dist/util-LfQirgDk.mjs +113 -0
- package/dist/util-LfQirgDk.mjs.map +1 -0
- package/dist/wasm.d.mts +4 -4
- package/dist/wasm.mjs +54 -55
- package/dist/wasm.mjs.map +1 -1
- package/dist/websocket-CmeqvAkp.d.mts +27 -0
- package/package.json +31 -9
- package/dist/builder-q0VsvnYE.mjs.map +0 -1
- package/dist/client-Bd8B1mYm.mjs.map +0 -1
- package/dist/error-B49rbDzT.d.mts +0 -7
- package/dist/error-CwBxle6N.mjs.map +0 -1
- package/dist/error-DZEs5OuZ.mjs +0 -24
- package/dist/error-DZEs5OuZ.mjs.map +0 -1
- package/dist/event-CzoQEFMF.mjs.map +0 -1
- package/dist/filter-BxwjEkZp.mjs.map +0 -1
- package/dist/filter-DUEKP3q1.d.mts +0 -40
- package/dist/gossip-3xkRRCm_.mjs.map +0 -1
- package/dist/http-D2rhokZo.mjs.map +0 -1
- package/dist/index-C0C4e-Gn.d.mts +0 -55
- package/dist/index-DgOugrpF.d.mts +0 -275
- package/dist/index-EwL6fHLo.d.mts +0 -267
- package/dist/index-k2YhCgsh.d.mts +0 -57
- package/dist/key-Dn-aX4x4.mjs.map +0 -1
- package/dist/kind-BLl4cLOc.mjs.map +0 -1
- package/dist/loaders-BNQszDAn.mjs.map +0 -1
- package/dist/memory-BCmhZPek.mjs.map +0 -1
- package/dist/message-C_Uj-Y-c.mjs.map +0 -1
- package/dist/pool-C_j1OmBB.mjs.map +0 -1
- package/dist/pool-DhRi7l-b.d.mts +0 -297
- package/dist/signer-CZ6K6FUc.mjs.map +0 -1
- package/dist/storage-DOy0iLRE.mjs +0 -870
- package/dist/storage-DOy0iLRE.mjs.map +0 -1
- package/dist/types-BzyADqnw.d.mts +0 -19
- package/dist/types-Dn24TPFs.d.mts +0 -43
- package/dist/util-6tfLQ9JF.mjs +0 -73
- package/dist/util-6tfLQ9JF.mjs.map +0 -1
|
@@ -1,60 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { g as NostrError, l as normalizeURL, o as isHex32 } from "./util-LfQirgDk.mjs";
|
|
2
|
+
import { r as isAddressableKind, t as Kind } from "./kind-Cxwhhkx8.mjs";
|
|
3
|
+
import { _ as formatEventAddress, f as sortedEvents } from "./event-D8Nfoh4K.mjs";
|
|
4
|
+
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
5
5
|
import { Nip19Error, decode, npubEncode } from "./nips/nip19.mjs";
|
|
6
6
|
import { parseRelayList } from "./nips/nip65.mjs";
|
|
7
|
+
import { r as throwIfAborted } from "./abort-Ba8tDrOI.mjs";
|
|
7
8
|
import { parseDmRelayList } from "./nips/nip17.mjs";
|
|
8
|
-
import { n as toStorageError } from "./error-
|
|
9
|
-
//#region src/loaders/cache.ts
|
|
10
|
-
function keyOf(spec) {
|
|
11
|
-
return `${spec.kind}:${spec.pubkey.toLowerCase()}:${spec.dTag ?? ""}`;
|
|
12
|
-
}
|
|
13
|
-
/** In-memory replaceable-event cache used by loaders (no DOM / localStorage). */
|
|
14
|
-
var ReplaceableCache = class {
|
|
15
|
-
#events = /* @__PURE__ */ new Map();
|
|
16
|
-
get(spec) {
|
|
17
|
-
return this.#events.get(keyOf(spec));
|
|
18
|
-
}
|
|
19
|
-
set(spec, event, fetchedAt = Math.floor(Date.now() / 1e3)) {
|
|
20
|
-
this.#events.set(keyOf(spec), {
|
|
21
|
-
event,
|
|
22
|
-
fetchedAt
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
/** Keep newer event by created_at. */
|
|
26
|
-
putIfNewer(event, dTag) {
|
|
27
|
-
const spec = {
|
|
28
|
-
kind: event.kind,
|
|
29
|
-
pubkey: event.pubkey,
|
|
30
|
-
dTag
|
|
31
|
-
};
|
|
32
|
-
const prev = this.get(spec);
|
|
33
|
-
if (!prev?.event || isReplaceableWinner(event, prev.event)) this.set(spec, event);
|
|
34
|
-
}
|
|
35
|
-
clear(spec) {
|
|
36
|
-
if (spec) this.#events.delete(keyOf(spec));
|
|
37
|
-
else this.#events.clear();
|
|
38
|
-
}
|
|
39
|
-
get size() {
|
|
40
|
-
return this.#events.size;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
//#endregion
|
|
9
|
+
import { n as toStorageError } from "./error-DrEIM7FO.mjs";
|
|
44
10
|
//#region src/loaders/context.ts
|
|
45
|
-
/**
|
|
46
|
-
* Explicit dependency bag for loaders — never a module-level singleton.
|
|
47
|
-
*/
|
|
11
|
+
/** Internal dependency bag for loaders — never a module-level singleton. */
|
|
48
12
|
var LoaderContext = class {
|
|
49
13
|
pool;
|
|
50
|
-
|
|
14
|
+
index;
|
|
15
|
+
ingest;
|
|
51
16
|
staleAfterSec;
|
|
52
17
|
fetchTimeoutMs;
|
|
53
18
|
#relays;
|
|
54
19
|
constructor(opts) {
|
|
55
20
|
this.pool = opts.pool;
|
|
56
21
|
this.#relays = [...opts.relays];
|
|
57
|
-
this.
|
|
22
|
+
this.index = opts.index;
|
|
23
|
+
this.ingest = opts.ingest ?? ((event, relayUrl) => this.index.add(event, relayUrl));
|
|
58
24
|
this.staleAfterSec = opts.staleAfterSec ?? 172800;
|
|
59
25
|
this.fetchTimeoutMs = opts.fetchTimeoutMs ?? 4400;
|
|
60
26
|
}
|
|
@@ -81,42 +47,60 @@ function dFilter(kind, identifier) {
|
|
|
81
47
|
if (isAddressableKind(kind)) return { "#d": [identifier] };
|
|
82
48
|
return {};
|
|
83
49
|
}
|
|
50
|
+
function addressRef(kind, pubkey, identifier) {
|
|
51
|
+
return formatEventAddress(kind, pubkey.toLowerCase(), identifier);
|
|
52
|
+
}
|
|
84
53
|
function parseRef(ref) {
|
|
85
54
|
if (typeof ref === "string") {
|
|
86
|
-
if (isHex32(ref))
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
55
|
+
if (isHex32(ref.toLowerCase())) {
|
|
56
|
+
const id = ref.toLowerCase();
|
|
57
|
+
return {
|
|
58
|
+
filter: { ids: [id] },
|
|
59
|
+
hints: [],
|
|
60
|
+
lookup: (ctx) => ctx.index.get(id),
|
|
61
|
+
cacheKey: `id:${id}`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
91
64
|
const decoded = decode(ref);
|
|
92
65
|
switch (decoded.type) {
|
|
93
66
|
case "note": return {
|
|
94
67
|
filter: { ids: [decoded.data] },
|
|
95
68
|
hints: [],
|
|
69
|
+
lookup: (ctx) => ctx.index.get(decoded.data),
|
|
96
70
|
cacheKey: `id:${decoded.data}`
|
|
97
71
|
};
|
|
98
72
|
case "nevent": return {
|
|
99
73
|
filter: { ids: [decoded.data.id] },
|
|
100
74
|
hints: decoded.data.relays ?? [],
|
|
75
|
+
lookup: (ctx) => ctx.index.get(decoded.data.id),
|
|
101
76
|
cacheKey: `id:${decoded.data.id}`
|
|
102
77
|
};
|
|
103
|
-
case "naddr":
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
78
|
+
case "naddr": {
|
|
79
|
+
const addr = addressRef(decoded.data.kind, decoded.data.pubkey, decoded.data.identifier);
|
|
80
|
+
return {
|
|
81
|
+
filter: {
|
|
82
|
+
authors: [decoded.data.pubkey.toLowerCase()],
|
|
83
|
+
kinds: [decoded.data.kind],
|
|
84
|
+
...dFilter(decoded.data.kind, decoded.data.identifier)
|
|
85
|
+
},
|
|
86
|
+
hints: decoded.data.relays ?? [],
|
|
87
|
+
lookup: (ctx) => ctx.index.getByAddress(addr),
|
|
88
|
+
cacheKey: `addr:${addr}`
|
|
89
|
+
};
|
|
90
|
+
}
|
|
112
91
|
default: throw new Nip19Error(`cannot load event from ${decoded.type}`);
|
|
113
92
|
}
|
|
114
93
|
}
|
|
115
|
-
if ("id" in ref)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
94
|
+
if ("id" in ref) {
|
|
95
|
+
const id = ref.id.toLowerCase();
|
|
96
|
+
return {
|
|
97
|
+
filter: { ids: [id] },
|
|
98
|
+
hints: ref.relays ?? [],
|
|
99
|
+
lookup: (ctx) => ctx.index.get(id),
|
|
100
|
+
cacheKey: `id:${id}`
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const addr = addressRef(ref.kind, ref.pubkey, ref.identifier);
|
|
120
104
|
return {
|
|
121
105
|
filter: {
|
|
122
106
|
authors: [ref.pubkey.toLowerCase()],
|
|
@@ -124,189 +108,39 @@ function parseRef(ref) {
|
|
|
124
108
|
...dFilter(ref.kind, ref.identifier)
|
|
125
109
|
},
|
|
126
110
|
hints: ref.relays ?? [],
|
|
127
|
-
|
|
111
|
+
lookup: (ctx) => ctx.index.getByAddress(addr),
|
|
112
|
+
cacheKey: `addr:${addr}`
|
|
128
113
|
};
|
|
129
114
|
}
|
|
130
115
|
function createEventLoader(ctx) {
|
|
131
116
|
const inflight = /* @__PURE__ */ new Map();
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
117
|
+
return { load(ref) {
|
|
118
|
+
const parsed = parseRef(ref);
|
|
119
|
+
const hit = parsed.lookup(ctx);
|
|
120
|
+
if (hit !== void 0) return Promise.resolve(hit);
|
|
121
|
+
const pending = inflight.get(parsed.cacheKey);
|
|
122
|
+
if (pending !== void 0) return pending;
|
|
123
|
+
const p = (async () => {
|
|
124
|
+
try {
|
|
140
125
|
const relays = [.../* @__PURE__ */ new Set([...parsed.hints, ...ctx.relays])];
|
|
141
|
-
if (relays.length === 0) return
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
cache.set(parsed.cacheKey, best);
|
|
146
|
-
return best;
|
|
147
|
-
})().finally(() => inflight.delete(parsed.cacheKey));
|
|
148
|
-
inflight.set(parsed.cacheKey, p);
|
|
149
|
-
return p;
|
|
150
|
-
},
|
|
151
|
-
clearAll() {
|
|
152
|
-
inflight.clear();
|
|
153
|
-
cache.clear();
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
//#endregion
|
|
158
|
-
//#region src/loaders/dataloader.ts
|
|
159
|
-
var LoaderError = class extends NostrError {};
|
|
160
|
-
var DataLoader = class {
|
|
161
|
-
#batchLoadFn;
|
|
162
|
-
#cacheKeyFn;
|
|
163
|
-
#maxBatchSize;
|
|
164
|
-
#useCache;
|
|
165
|
-
#cache = /* @__PURE__ */ new Map();
|
|
166
|
-
#inflight = /* @__PURE__ */ new Map();
|
|
167
|
-
#queue = [];
|
|
168
|
-
#scheduled = false;
|
|
169
|
-
constructor(batchLoadFn, options = {}) {
|
|
170
|
-
this.#batchLoadFn = batchLoadFn;
|
|
171
|
-
this.#cacheKeyFn = options.cacheKeyFn ?? ((k) => k);
|
|
172
|
-
this.#maxBatchSize = options.maxBatchSize ?? Infinity;
|
|
173
|
-
this.#useCache = options.cache !== false;
|
|
174
|
-
}
|
|
175
|
-
load(key) {
|
|
176
|
-
const cacheKey = this.#cacheKeyFn(key);
|
|
177
|
-
if (this.#useCache) {
|
|
178
|
-
const cached = this.#cache.get(cacheKey);
|
|
179
|
-
if (cached) return cached;
|
|
180
|
-
}
|
|
181
|
-
const inflight = this.#inflight.get(cacheKey);
|
|
182
|
-
if (inflight) return inflight;
|
|
183
|
-
const promise = new Promise((resolve, reject) => {
|
|
184
|
-
this.#queue.push({
|
|
185
|
-
key,
|
|
186
|
-
resolve,
|
|
187
|
-
reject
|
|
188
|
-
});
|
|
189
|
-
if (this.#queue.length >= this.#maxBatchSize) this.#dispatch();
|
|
190
|
-
else if (!this.#scheduled) {
|
|
191
|
-
this.#scheduled = true;
|
|
192
|
-
queueMicrotask(() => this.#dispatch());
|
|
193
|
-
}
|
|
194
|
-
}).finally(() => {
|
|
195
|
-
this.#inflight.delete(cacheKey);
|
|
196
|
-
});
|
|
197
|
-
this.#inflight.set(cacheKey, promise);
|
|
198
|
-
if (this.#useCache) this.#cache.set(cacheKey, promise);
|
|
199
|
-
return promise;
|
|
200
|
-
}
|
|
201
|
-
clear(key) {
|
|
202
|
-
this.#cache.delete(this.#cacheKeyFn(key));
|
|
203
|
-
this.#inflight.delete(this.#cacheKeyFn(key));
|
|
204
|
-
}
|
|
205
|
-
clearAll() {
|
|
206
|
-
this.#cache.clear();
|
|
207
|
-
this.#inflight.clear();
|
|
208
|
-
}
|
|
209
|
-
#dispatch() {
|
|
210
|
-
this.#scheduled = false;
|
|
211
|
-
const batch = this.#queue.splice(0, this.#maxBatchSize);
|
|
212
|
-
if (batch.length === 0) return;
|
|
213
|
-
const keys = batch.map((b) => b.key);
|
|
214
|
-
this.#batchLoadFn(keys).then((values) => {
|
|
215
|
-
if (values.length !== keys.length) {
|
|
216
|
-
const err = new LoaderError(`DataLoader batch function must return array of length ${keys.length}, got ${values.length}`);
|
|
217
|
-
for (const item of batch) item.reject(err);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
for (let i = 0; i < batch.length; i++) {
|
|
221
|
-
const value = values[i];
|
|
222
|
-
if (value instanceof Error) batch[i].reject(value);
|
|
223
|
-
else batch[i].resolve(value);
|
|
224
|
-
}
|
|
225
|
-
}).catch((err) => {
|
|
226
|
-
for (const item of batch) item.reject(err);
|
|
227
|
-
});
|
|
228
|
-
if (this.#queue.length > 0) {
|
|
229
|
-
this.#scheduled = true;
|
|
230
|
-
queueMicrotask(() => this.#dispatch());
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
//#endregion
|
|
235
|
-
//#region src/loaders/replaceable.ts
|
|
236
|
-
/**
|
|
237
|
-
* Batch-fetch replaceable events (kind + authors) via the pool, with cache.
|
|
238
|
-
* Durable cache is ReplaceableCache; DataLoader only coalesces in-flight requests.
|
|
239
|
-
*/
|
|
240
|
-
function createReplaceableLoader(ctx, kind) {
|
|
241
|
-
const loader = new DataLoader(async (keys) => {
|
|
242
|
-
const authors = [...new Set(keys.map((k) => k.pubkey))];
|
|
243
|
-
const hintRelays = [...new Set(keys.flatMap((k) => k.hints ?? []).concat(ctx.relays))];
|
|
244
|
-
const filter = {
|
|
245
|
-
kinds: [kind],
|
|
246
|
-
authors
|
|
247
|
-
};
|
|
248
|
-
const events = hintRelays.length > 0 ? await ctx.pool.fetch(hintRelays, [filter], { timeoutMs: ctx.fetchTimeoutMs }) : [];
|
|
249
|
-
const best = /* @__PURE__ */ new Map();
|
|
250
|
-
for (const event of events) {
|
|
251
|
-
if (event.kind !== kind) continue;
|
|
252
|
-
const prev = best.get(event.pubkey);
|
|
253
|
-
if (!prev || isReplaceableWinner(event, prev)) best.set(event.pubkey, event);
|
|
254
|
-
}
|
|
255
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
256
|
-
return keys.map((k) => {
|
|
257
|
-
const event = best.get(k.pubkey) ?? null;
|
|
258
|
-
ctx.cache.set({
|
|
259
|
-
kind,
|
|
260
|
-
pubkey: k.pubkey,
|
|
261
|
-
dTag: event ? getDTag(event.tags) ?? void 0 : void 0
|
|
262
|
-
}, event, now);
|
|
263
|
-
return event;
|
|
264
|
-
});
|
|
265
|
-
}, {
|
|
266
|
-
cache: false,
|
|
267
|
-
cacheKeyFn: (k) => `${kind}:${k.pubkey}:${(k.hints ?? []).join(",")}`,
|
|
268
|
-
maxBatchSize: 50
|
|
269
|
-
});
|
|
270
|
-
return {
|
|
271
|
-
async load(pubkey, opts) {
|
|
272
|
-
const pk = pubkey.toLowerCase();
|
|
273
|
-
const style = opts?.style ?? "default";
|
|
274
|
-
const cached = ctx.cache.get({
|
|
275
|
-
kind,
|
|
276
|
-
pubkey: pk
|
|
277
|
-
});
|
|
278
|
-
if (style === "cache-only") return {
|
|
279
|
-
event: cached?.event ?? null,
|
|
280
|
-
fresh: false
|
|
281
|
-
};
|
|
282
|
-
if (style === "default" && cached && ctx.isFresh(cached.fetchedAt)) return {
|
|
283
|
-
event: cached.event,
|
|
284
|
-
fresh: false
|
|
285
|
-
};
|
|
286
|
-
return {
|
|
287
|
-
event: await loader.load({
|
|
288
|
-
pubkey: pk,
|
|
289
|
-
hints: opts?.hints
|
|
290
|
-
}),
|
|
291
|
-
fresh: true
|
|
292
|
-
};
|
|
293
|
-
},
|
|
294
|
-
clear(pubkey) {
|
|
295
|
-
if (pubkey) {
|
|
296
|
-
const pk = pubkey.toLowerCase();
|
|
297
|
-
loader.clear({ pubkey: pk });
|
|
298
|
-
ctx.cache.clear({
|
|
299
|
-
kind,
|
|
300
|
-
pubkey: pk
|
|
126
|
+
if (relays.length === 0) return;
|
|
127
|
+
await ctx.pool.fetch(relays, [parsed.filter], {
|
|
128
|
+
timeoutMs: ctx.fetchTimeoutMs,
|
|
129
|
+
onevent: (event, relayUrl) => ctx.ingest(event, relayUrl)
|
|
301
130
|
});
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
131
|
+
return parsed.lookup(ctx);
|
|
132
|
+
} finally {
|
|
133
|
+
inflight.delete(parsed.cacheKey);
|
|
134
|
+
}
|
|
135
|
+
})();
|
|
136
|
+
inflight.set(parsed.cacheKey, p);
|
|
137
|
+
return p;
|
|
138
|
+
} };
|
|
305
139
|
}
|
|
306
140
|
//#endregion
|
|
307
141
|
//#region src/loaders/lists.ts
|
|
308
142
|
function fromTags(event, map) {
|
|
309
|
-
if (
|
|
143
|
+
if (event === void 0) return [];
|
|
310
144
|
const out = [];
|
|
311
145
|
for (const tag of event.tags) {
|
|
312
146
|
const item = map(tag);
|
|
@@ -314,33 +148,33 @@ function fromTags(event, map) {
|
|
|
314
148
|
}
|
|
315
149
|
return out;
|
|
316
150
|
}
|
|
317
|
-
function createListLoaders(
|
|
318
|
-
const followsLoader =
|
|
319
|
-
const muteLoader =
|
|
320
|
-
const relayListLoader =
|
|
321
|
-
const dmRelayListLoader =
|
|
151
|
+
function createListLoaders(replaceable) {
|
|
152
|
+
const followsLoader = replaceable(Kind.Contacts);
|
|
153
|
+
const muteLoader = replaceable(Kind.MuteList);
|
|
154
|
+
const relayListLoader = replaceable(Kind.RelayList);
|
|
155
|
+
const dmRelayListLoader = replaceable(Kind.DirectMessageRelaysList);
|
|
322
156
|
return {
|
|
323
157
|
async follows(pubkey, opts) {
|
|
324
|
-
const { event, fresh } = await followsLoader
|
|
158
|
+
const { event, fresh } = await followsLoader(pubkey, opts);
|
|
325
159
|
return {
|
|
326
160
|
event,
|
|
327
161
|
fresh,
|
|
328
|
-
items: fromTags(event, (tag) => tag[0] === "p" && tag[1] && isHex32(tag[1]) ? tag[1].toLowerCase() : void 0)
|
|
162
|
+
items: fromTags(event, (tag) => tag[0] === "p" && tag[1] !== void 0 && isHex32(tag[1].toLowerCase()) ? tag[1].toLowerCase() : void 0)
|
|
329
163
|
};
|
|
330
164
|
},
|
|
331
165
|
async muteList(pubkey, opts) {
|
|
332
|
-
const { event, fresh } = await muteLoader
|
|
166
|
+
const { event, fresh } = await muteLoader(pubkey, opts);
|
|
333
167
|
return {
|
|
334
168
|
event,
|
|
335
169
|
fresh,
|
|
336
170
|
items: fromTags(event, (tag) => {
|
|
337
|
-
if (
|
|
171
|
+
if (tag[1] === void 0 || tag[1] === "") return;
|
|
338
172
|
switch (tag[0]) {
|
|
339
|
-
case "p": return isHex32(tag[1]) ? {
|
|
173
|
+
case "p": return isHex32(tag[1].toLowerCase()) ? {
|
|
340
174
|
label: "pubkey",
|
|
341
175
|
value: tag[1].toLowerCase()
|
|
342
176
|
} : void 0;
|
|
343
|
-
case "e": return isHex32(tag[1]) ? {
|
|
177
|
+
case "e": return isHex32(tag[1].toLowerCase()) ? {
|
|
344
178
|
label: "thread",
|
|
345
179
|
value: tag[1].toLowerCase()
|
|
346
180
|
} : void 0;
|
|
@@ -358,7 +192,7 @@ function createListLoaders(ctx) {
|
|
|
358
192
|
};
|
|
359
193
|
},
|
|
360
194
|
async relayList(pubkey, opts) {
|
|
361
|
-
const { event, fresh } = await relayListLoader
|
|
195
|
+
const { event, fresh } = await relayListLoader(pubkey, opts);
|
|
362
196
|
let items = [];
|
|
363
197
|
if (event) try {
|
|
364
198
|
items = parseRelayList(event);
|
|
@@ -382,7 +216,7 @@ function createListLoaders(ctx) {
|
|
|
382
216
|
};
|
|
383
217
|
},
|
|
384
218
|
async dmRelayList(pubkey, opts) {
|
|
385
|
-
const { event, fresh } = await dmRelayListLoader
|
|
219
|
+
const { event, fresh } = await dmRelayListLoader(pubkey, opts);
|
|
386
220
|
let items = [];
|
|
387
221
|
if (event) try {
|
|
388
222
|
items = parseDmRelayList(event);
|
|
@@ -399,6 +233,7 @@ function createListLoaders(ctx) {
|
|
|
399
233
|
}
|
|
400
234
|
//#endregion
|
|
401
235
|
//#region src/loaders/profile.ts
|
|
236
|
+
/** A {@link NostrUser} with no metadata — used when no kind:0 event exists. */
|
|
402
237
|
function bareNostrUser(pubkey) {
|
|
403
238
|
const pk = pubkey.toLowerCase();
|
|
404
239
|
let npub;
|
|
@@ -413,33 +248,37 @@ function bareNostrUser(pubkey) {
|
|
|
413
248
|
shortName: npub.startsWith("npub1") ? `${npub.slice(0, 8)}…${npub.slice(-4)}` : pk.slice(0, 8),
|
|
414
249
|
metadata: {},
|
|
415
250
|
lastUpdated: 0,
|
|
416
|
-
event:
|
|
251
|
+
event: void 0,
|
|
417
252
|
fresh: false
|
|
418
253
|
};
|
|
419
254
|
}
|
|
255
|
+
function nonEmpty(value) {
|
|
256
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
257
|
+
}
|
|
420
258
|
function parseMetadata(content) {
|
|
421
259
|
try {
|
|
422
260
|
const obj = JSON.parse(content);
|
|
423
|
-
|
|
261
|
+
if (typeof obj !== "object" || obj === null) return {};
|
|
262
|
+
return obj;
|
|
424
263
|
} catch {
|
|
425
264
|
return {};
|
|
426
265
|
}
|
|
427
266
|
}
|
|
428
|
-
function createProfileLoader(
|
|
429
|
-
const loader =
|
|
267
|
+
function createProfileLoader(replaceable) {
|
|
268
|
+
const loader = replaceable(Kind.Metadata);
|
|
430
269
|
return { async load(pubkey, opts) {
|
|
431
270
|
const base = bareNostrUser(pubkey);
|
|
432
|
-
const { event, fresh } = await loader
|
|
271
|
+
const { event, fresh } = await loader(pubkey, opts);
|
|
433
272
|
if (!event) return {
|
|
434
273
|
...base,
|
|
435
274
|
fresh
|
|
436
275
|
};
|
|
437
276
|
const metadata = parseMetadata(event.content);
|
|
438
|
-
const display = metadata.display_name
|
|
277
|
+
const display = nonEmpty(metadata.display_name) ?? nonEmpty(metadata.name);
|
|
439
278
|
return {
|
|
440
279
|
...base,
|
|
441
|
-
shortName: display
|
|
442
|
-
image: metadata.picture,
|
|
280
|
+
shortName: display ?? base.shortName,
|
|
281
|
+
...metadata.picture === void 0 ? {} : { image: metadata.picture },
|
|
443
282
|
metadata,
|
|
444
283
|
lastUpdated: event.created_at,
|
|
445
284
|
event,
|
|
@@ -448,8 +287,177 @@ function createProfileLoader(ctx) {
|
|
|
448
287
|
} };
|
|
449
288
|
}
|
|
450
289
|
//#endregion
|
|
290
|
+
//#region src/loaders/dataloader.ts
|
|
291
|
+
var LoaderError = class extends NostrError {
|
|
292
|
+
name = "LoaderError";
|
|
293
|
+
};
|
|
294
|
+
var DataLoader = class {
|
|
295
|
+
#batchLoadFn;
|
|
296
|
+
#cacheKeyFn;
|
|
297
|
+
#maxBatchSize;
|
|
298
|
+
#useCache;
|
|
299
|
+
#cache = /* @__PURE__ */ new Map();
|
|
300
|
+
#inflight = /* @__PURE__ */ new Map();
|
|
301
|
+
#queue = [];
|
|
302
|
+
#scheduled = false;
|
|
303
|
+
constructor(batchLoadFn, options = {}) {
|
|
304
|
+
this.#batchLoadFn = batchLoadFn;
|
|
305
|
+
this.#cacheKeyFn = options.cacheKeyFn ?? ((k) => k);
|
|
306
|
+
this.#maxBatchSize = options.maxBatchSize ?? Number.POSITIVE_INFINITY;
|
|
307
|
+
this.#useCache = options.cache !== false;
|
|
308
|
+
}
|
|
309
|
+
async load(key) {
|
|
310
|
+
const cacheKey = this.#cacheKeyFn(key);
|
|
311
|
+
if (this.#useCache) {
|
|
312
|
+
const cached = this.#cache.get(cacheKey);
|
|
313
|
+
if (cached) return cached;
|
|
314
|
+
}
|
|
315
|
+
const inflight = this.#inflight.get(cacheKey);
|
|
316
|
+
if (inflight) return inflight;
|
|
317
|
+
const pending = new Promise((resolve, reject) => {
|
|
318
|
+
this.#queue.push({
|
|
319
|
+
key,
|
|
320
|
+
resolve,
|
|
321
|
+
reject
|
|
322
|
+
});
|
|
323
|
+
if (this.#queue.length >= this.#maxBatchSize) this.#dispatch();
|
|
324
|
+
else if (!this.#scheduled) {
|
|
325
|
+
this.#scheduled = true;
|
|
326
|
+
queueMicrotask(() => this.#dispatch());
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
const promise = (async () => {
|
|
330
|
+
try {
|
|
331
|
+
return await pending;
|
|
332
|
+
} finally {
|
|
333
|
+
this.#inflight.delete(cacheKey);
|
|
334
|
+
}
|
|
335
|
+
})();
|
|
336
|
+
this.#inflight.set(cacheKey, promise);
|
|
337
|
+
if (this.#useCache) this.#cache.set(cacheKey, promise);
|
|
338
|
+
return promise;
|
|
339
|
+
}
|
|
340
|
+
clear(key) {
|
|
341
|
+
this.#cache.delete(this.#cacheKeyFn(key));
|
|
342
|
+
this.#inflight.delete(this.#cacheKeyFn(key));
|
|
343
|
+
}
|
|
344
|
+
clearAll() {
|
|
345
|
+
this.#cache.clear();
|
|
346
|
+
this.#inflight.clear();
|
|
347
|
+
}
|
|
348
|
+
#dispatch() {
|
|
349
|
+
this.#scheduled = false;
|
|
350
|
+
const batch = this.#queue.splice(0, this.#maxBatchSize);
|
|
351
|
+
if (batch.length === 0) return;
|
|
352
|
+
const keys = batch.map((b) => b.key);
|
|
353
|
+
(async () => {
|
|
354
|
+
let values;
|
|
355
|
+
try {
|
|
356
|
+
values = await this.#batchLoadFn(keys);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
for (const item of batch) item.reject(error);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (values.length !== keys.length) {
|
|
362
|
+
const err = new LoaderError(`DataLoader batch function must return array of length ${keys.length}, got ${values.length}`);
|
|
363
|
+
for (const item of batch) item.reject(err);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
for (const [i, value] of values.entries()) {
|
|
367
|
+
const item = batch[i];
|
|
368
|
+
if (item === void 0) continue;
|
|
369
|
+
if (value instanceof Error) item.reject(value);
|
|
370
|
+
else item.resolve(value);
|
|
371
|
+
}
|
|
372
|
+
})();
|
|
373
|
+
if (this.#queue.length > 0) {
|
|
374
|
+
this.#scheduled = true;
|
|
375
|
+
queueMicrotask(() => this.#dispatch());
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/loaders/replaceable.ts
|
|
381
|
+
/** FIFO cap on per-address fetch records (misses included). */
|
|
382
|
+
const MAX_FETCHED_AT = 1e4;
|
|
383
|
+
/**
|
|
384
|
+
* Batch-fetch replaceable events (kind + authors) via the pool. Fetched events are written into the
|
|
385
|
+
* reactive index (`onevent` records `seenOn`) and the winner is read back from it; the index — not
|
|
386
|
+
* the loader — owns the stored value and winner selection. The loader only tracks per-address
|
|
387
|
+
* `fetchedAt` timestamps so a recent fetch (including a miss) is not repeated within
|
|
388
|
+
* `staleAfterSec`.
|
|
389
|
+
*/
|
|
390
|
+
function createReplaceableLoader(ctx, kind) {
|
|
391
|
+
const fetchedAt = /* @__PURE__ */ new Map();
|
|
392
|
+
const markFetched = (addr, hit, now) => {
|
|
393
|
+
fetchedAt.delete(addr);
|
|
394
|
+
fetchedAt.set(addr, {
|
|
395
|
+
at: now,
|
|
396
|
+
hit
|
|
397
|
+
});
|
|
398
|
+
while (fetchedAt.size > MAX_FETCHED_AT) {
|
|
399
|
+
const oldest = fetchedAt.keys().next().value;
|
|
400
|
+
if (oldest === void 0) break;
|
|
401
|
+
fetchedAt.delete(oldest);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
const loader = new DataLoader(async (keys) => {
|
|
405
|
+
const authors = [...new Set(keys.map((k) => k.pubkey))];
|
|
406
|
+
const relays = [.../* @__PURE__ */ new Set([...keys.flatMap((k) => k.hints ?? []), ...ctx.relays])];
|
|
407
|
+
if (relays.length > 0) {
|
|
408
|
+
const filter = {
|
|
409
|
+
kinds: [kind],
|
|
410
|
+
authors
|
|
411
|
+
};
|
|
412
|
+
await ctx.pool.fetch(relays, [filter], {
|
|
413
|
+
timeoutMs: ctx.fetchTimeoutMs,
|
|
414
|
+
onevent: (event, relayUrl) => ctx.ingest(event, relayUrl)
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
418
|
+
return keys.map((k) => {
|
|
419
|
+
const addr = formatEventAddress(kind, k.pubkey, "");
|
|
420
|
+
const event = ctx.index.getByAddress(addr);
|
|
421
|
+
markFetched(addr, event !== void 0, now);
|
|
422
|
+
return {
|
|
423
|
+
event,
|
|
424
|
+
fresh: true
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
}, {
|
|
428
|
+
cache: false,
|
|
429
|
+
cacheKeyFn: (k) => `${kind}:${k.pubkey}:${(k.hints ?? []).join(",")}`,
|
|
430
|
+
maxBatchSize: 50
|
|
431
|
+
});
|
|
432
|
+
return async (pubkey, opts) => {
|
|
433
|
+
const pk = pubkey.toLowerCase();
|
|
434
|
+
const address = formatEventAddress(kind, pk, "");
|
|
435
|
+
const style = opts?.style ?? "default";
|
|
436
|
+
const current = () => ctx.index.getByAddress(address);
|
|
437
|
+
if (style === "cache-only") return {
|
|
438
|
+
event: current(),
|
|
439
|
+
fresh: false
|
|
440
|
+
};
|
|
441
|
+
if (style === "default") {
|
|
442
|
+
const rec = fetchedAt.get(address);
|
|
443
|
+
const event = current();
|
|
444
|
+
if (rec !== void 0 && ctx.isFresh(rec.at) && (!rec.hit || event !== void 0)) return {
|
|
445
|
+
event,
|
|
446
|
+
fresh: false
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return loader.load({
|
|
450
|
+
pubkey: pk,
|
|
451
|
+
hints: opts?.hints
|
|
452
|
+
});
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
//#endregion
|
|
451
456
|
//#region src/loaders/outbox.ts
|
|
452
|
-
|
|
457
|
+
/** Outbox feed lifecycle error (e.g. use after close). */
|
|
458
|
+
var OutboxError = class extends NostrError {
|
|
459
|
+
name = "OutboxError";
|
|
460
|
+
};
|
|
453
461
|
function boundKey(pubkey, kind) {
|
|
454
462
|
return `${pubkey.toLowerCase()}:${kind}`;
|
|
455
463
|
}
|
|
@@ -463,10 +471,9 @@ function canonicalRelayUrls(urls) {
|
|
|
463
471
|
return out;
|
|
464
472
|
}
|
|
465
473
|
/**
|
|
466
|
-
* Group authors by outbox relay (discovery fallback).
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
* Returns Map<relayUrl, authors[]>.
|
|
474
|
+
* Group authors by outbox relay (discovery fallback). `prefer` reorders existing candidates only;
|
|
475
|
+
* it never appends a URL that is not already in the author's outbox or discovery list. Returns
|
|
476
|
+
* Map<relayUrl, authors[]>.
|
|
470
477
|
*/
|
|
471
478
|
function groupAuthorsByOutboxRelay(authors, gossip, discoveryRelays, maxRelaysPerAuthor = 3, prefer = []) {
|
|
472
479
|
const leftover = new Set((gossip.route({ authors: authors.map((a) => a.toLowerCase()) }).remainder?.authors ?? []).map((a) => a.toLowerCase()));
|
|
@@ -480,7 +487,7 @@ function groupAuthorsByOutboxRelay(authors, gossip, discoveryRelays, maxRelaysPe
|
|
|
480
487
|
const rest = [];
|
|
481
488
|
for (const url of urls) if (preferSet.has(url)) preferred.push(url);
|
|
482
489
|
else rest.push(url);
|
|
483
|
-
for (const url of preferred
|
|
490
|
+
for (const url of [...preferred, ...rest].slice(0, maxRelaysPerAuthor)) {
|
|
484
491
|
const list = map.get(url) ?? [];
|
|
485
492
|
if (!list.includes(pk)) list.push(pk);
|
|
486
493
|
map.set(url, list);
|
|
@@ -489,8 +496,8 @@ function groupAuthorsByOutboxRelay(authors, gossip, discoveryRelays, maxRelaysPe
|
|
|
489
496
|
return map;
|
|
490
497
|
}
|
|
491
498
|
/**
|
|
492
|
-
* Outbox-model feed: history sync + live subscription for a set of authors,
|
|
493
|
-
*
|
|
499
|
+
* Outbox-model feed: history sync + live subscription for a set of authors, routed via NIP-65 write
|
|
500
|
+
* relays (with discovery fallback).
|
|
494
501
|
*/
|
|
495
502
|
var OutboxFeed = class {
|
|
496
503
|
#pool;
|
|
@@ -502,6 +509,7 @@ var OutboxFeed = class {
|
|
|
502
509
|
#timeoutMs;
|
|
503
510
|
#onEvent;
|
|
504
511
|
#observe;
|
|
512
|
+
#seen;
|
|
505
513
|
#applySync;
|
|
506
514
|
#hydrate;
|
|
507
515
|
#bounds = /* @__PURE__ */ new Map();
|
|
@@ -519,6 +527,7 @@ var OutboxFeed = class {
|
|
|
519
527
|
this.#timeoutMs = opts.fetchTimeoutMs ?? 4400;
|
|
520
528
|
this.#onEvent = opts.onEvent;
|
|
521
529
|
this.#observe = opts.observe;
|
|
530
|
+
this.#seen = opts.seen;
|
|
522
531
|
this.#applySync = opts.applySync;
|
|
523
532
|
this.#hydrate = opts.hydrate;
|
|
524
533
|
}
|
|
@@ -535,9 +544,7 @@ var OutboxFeed = class {
|
|
|
535
544
|
getBound(pubkey, kind) {
|
|
536
545
|
return this.#bounds.get(boundKey(pubkey, kind));
|
|
537
546
|
}
|
|
538
|
-
/**
|
|
539
|
-
* Load NIP-65 relay lists for authors that have no outbox routes yet.
|
|
540
|
-
*/
|
|
547
|
+
/** Load NIP-65 relay lists for authors that have no outbox routes yet. */
|
|
541
548
|
async hydrate() {
|
|
542
549
|
this.#assertOpen();
|
|
543
550
|
if (!this.#hydrate) return;
|
|
@@ -545,47 +552,48 @@ var OutboxFeed = class {
|
|
|
545
552
|
if (missing.length === 0) return;
|
|
546
553
|
await this.#hydrate(missing);
|
|
547
554
|
}
|
|
548
|
-
/**
|
|
549
|
-
* One-shot history pull. Persistence is `applySync`; bounds move only for events it returns.
|
|
550
|
-
*/
|
|
555
|
+
/** One-shot history pull. Persistence is `applySync`; bounds move only for events it returns. */
|
|
551
556
|
async sync(opts) {
|
|
552
557
|
this.#assertOpen();
|
|
553
|
-
if (
|
|
558
|
+
if (opts?.skipHydrate !== true) await this.hydrate();
|
|
554
559
|
await this.#hydrateBoundsFromStore();
|
|
555
560
|
const byRelay = groupAuthorsByOutboxRelay(this.#authors, this.#gossip, this.#discovery, this.#maxRelays, this.#pool.connectedUrls());
|
|
556
561
|
if (byRelay.size === 0) return [];
|
|
557
562
|
const byId = /* @__PURE__ */ new Map();
|
|
558
563
|
await Promise.all([...byRelay.entries()].map(async ([url, authors]) => {
|
|
559
|
-
|
|
564
|
+
throwIfAborted(opts?.signal);
|
|
560
565
|
const filters = this.#syncFilters(authors, opts);
|
|
561
566
|
try {
|
|
562
567
|
const batch = await this.#pool.fetch([url], filters, {
|
|
563
568
|
timeoutMs: this.#timeoutMs,
|
|
564
|
-
signal: opts?.signal
|
|
569
|
+
signal: opts?.signal,
|
|
570
|
+
onevent: (event, relayUrl) => invokeSafely(() => this.#seen?.(event, relayUrl))
|
|
565
571
|
});
|
|
566
572
|
for (const event of batch) byId.set(event.id, event);
|
|
567
|
-
} catch {
|
|
573
|
+
} catch (error) {
|
|
574
|
+
if (opts?.signal?.aborted === true) throw error;
|
|
575
|
+
}
|
|
568
576
|
}));
|
|
569
577
|
const unique = [...byId.values()];
|
|
570
578
|
if (unique.length === 0) return [];
|
|
571
579
|
let applied = [];
|
|
572
580
|
if (this.#applySync) try {
|
|
573
581
|
applied = await this.#applySync(unique);
|
|
574
|
-
} catch (
|
|
575
|
-
throw toStorageError(
|
|
582
|
+
} catch (error) {
|
|
583
|
+
throw toStorageError(error);
|
|
576
584
|
}
|
|
577
585
|
const dirty = /* @__PURE__ */ new Set();
|
|
578
586
|
for (const event of applied) {
|
|
579
587
|
this.#updateBounds(event);
|
|
580
588
|
dirty.add(boundKey(event.pubkey, event.kind));
|
|
581
|
-
this.#onEvent?.(event);
|
|
589
|
+
invokeSafely(() => this.#onEvent?.(event));
|
|
582
590
|
}
|
|
583
591
|
await this.#persistBounds(dirty);
|
|
584
592
|
return sortedEvents(unique);
|
|
585
593
|
}
|
|
586
594
|
/**
|
|
587
|
-
* Start a live subscription across outbox relays for the current authors.
|
|
588
|
-
*
|
|
595
|
+
* Start a live subscription across outbox relays for the current authors. Closes any previous
|
|
596
|
+
* live subscription.
|
|
589
597
|
*/
|
|
590
598
|
startLive(opts) {
|
|
591
599
|
this.#assertOpen();
|
|
@@ -602,10 +610,11 @@ var OutboxFeed = class {
|
|
|
602
610
|
};
|
|
603
611
|
closers.push(this.#pool.subscribe([url], [filter], {
|
|
604
612
|
signal: opts?.signal,
|
|
605
|
-
onevent: (event) => {
|
|
613
|
+
onevent: (event, relayUrl) => {
|
|
614
|
+
invokeSafely(() => this.#seen?.(event, relayUrl));
|
|
606
615
|
if (seen.has(event.id)) return;
|
|
607
616
|
seen.add(event.id);
|
|
608
|
-
this.#noteEvent(event);
|
|
617
|
+
this.#noteEvent(event, relayUrl);
|
|
609
618
|
}
|
|
610
619
|
}));
|
|
611
620
|
}
|
|
@@ -653,7 +662,7 @@ var OutboxFeed = class {
|
|
|
653
662
|
const base = {
|
|
654
663
|
kinds: this.#kinds,
|
|
655
664
|
limit: opts?.limit ?? 50,
|
|
656
|
-
...opts?.until
|
|
665
|
+
...opts?.until === void 0 ? {} : { until: opts.until }
|
|
657
666
|
};
|
|
658
667
|
if (opts?.since !== void 0) return [{
|
|
659
668
|
...base,
|
|
@@ -672,7 +681,7 @@ var OutboxFeed = class {
|
|
|
672
681
|
complete = false;
|
|
673
682
|
break;
|
|
674
683
|
}
|
|
675
|
-
|
|
684
|
+
newest = Math.min(newest ?? b.newest, b.newest);
|
|
676
685
|
}
|
|
677
686
|
if (complete && newest !== void 0) {
|
|
678
687
|
bounded.push(pk);
|
|
@@ -697,15 +706,19 @@ var OutboxFeed = class {
|
|
|
697
706
|
authors: unbounded
|
|
698
707
|
}];
|
|
699
708
|
}
|
|
700
|
-
#noteEvent(event) {
|
|
709
|
+
#noteEvent(event, relayUrl) {
|
|
701
710
|
this.#updateBounds(event);
|
|
702
711
|
const bound = this.#bounds.get(boundKey(event.pubkey, event.kind));
|
|
703
|
-
if (bound)
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
712
|
+
if (bound) (async () => {
|
|
713
|
+
try {
|
|
714
|
+
await this.#storage.setOutboxBound(event.pubkey, event.kind, {
|
|
715
|
+
oldest: bound.oldest,
|
|
716
|
+
newest: bound.newest
|
|
717
|
+
});
|
|
718
|
+
} catch {}
|
|
719
|
+
})();
|
|
720
|
+
invokeSafely(() => this.#observe?.(event, relayUrl));
|
|
721
|
+
invokeSafely(() => this.#onEvent?.(event));
|
|
709
722
|
}
|
|
710
723
|
async #persistBounds(keys) {
|
|
711
724
|
const writes = [];
|
|
@@ -721,8 +734,8 @@ var OutboxFeed = class {
|
|
|
721
734
|
if (writes.length === 0) return;
|
|
722
735
|
try {
|
|
723
736
|
await Promise.all(writes);
|
|
724
|
-
} catch (
|
|
725
|
-
throw toStorageError(
|
|
737
|
+
} catch (error) {
|
|
738
|
+
throw toStorageError(error);
|
|
726
739
|
}
|
|
727
740
|
}
|
|
728
741
|
#updateBounds(event) {
|
|
@@ -748,20 +761,31 @@ function createOutboxFeed(opts) {
|
|
|
748
761
|
/** Build an instance-scoped loader suite (no module globals). */
|
|
749
762
|
function createLoaders(opts) {
|
|
750
763
|
const context = new LoaderContext(opts);
|
|
751
|
-
const
|
|
752
|
-
const
|
|
764
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
765
|
+
const replaceable = (kind) => {
|
|
766
|
+
let loader = byKind.get(kind);
|
|
767
|
+
if (loader === void 0) {
|
|
768
|
+
loader = createReplaceableLoader(context, kind);
|
|
769
|
+
byKind.set(kind, loader);
|
|
770
|
+
}
|
|
771
|
+
return loader;
|
|
772
|
+
};
|
|
773
|
+
const lists = createListLoaders(replaceable);
|
|
774
|
+
const profile = createProfileLoader(replaceable);
|
|
753
775
|
const event = createEventLoader(context);
|
|
754
776
|
return {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
777
|
+
follows: async (pubkey, o) => lists.follows(pubkey, o),
|
|
778
|
+
muteList: async (pubkey, o) => lists.muteList(pubkey, o),
|
|
779
|
+
relayList: async (pubkey, o) => lists.relayList(pubkey, o),
|
|
780
|
+
dmRelayList: async (pubkey, o) => lists.dmRelayList(pubkey, o),
|
|
781
|
+
profile: async (pubkey, o) => profile.load(pubkey, o),
|
|
782
|
+
event: (ref) => event.load(ref),
|
|
783
|
+
replaceable,
|
|
784
|
+
addRelay: (url) => context.addRelay(url),
|
|
785
|
+
removeRelay: (url) => context.removeRelay(url)
|
|
762
786
|
};
|
|
763
787
|
}
|
|
764
788
|
//#endregion
|
|
765
|
-
export { groupAuthorsByOutboxRelay as a,
|
|
789
|
+
export { groupAuthorsByOutboxRelay as a, createOutboxFeed as i, OutboxError as n, bareNostrUser as o, OutboxFeed as r, createLoaders as t };
|
|
766
790
|
|
|
767
|
-
//# sourceMappingURL=loaders-
|
|
791
|
+
//# sourceMappingURL=loaders-o6YXB_Ri.mjs.map
|