@qntx/nostr 0.6.0 → 0.7.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 → abort-DBq2STPj.mjs} +13 -6
- package/dist/abort-DBq2STPj.mjs.map +1 -0
- package/dist/{builder-9tjI6WQ3.mjs → builder-CdLE4J6O.mjs} +11 -17
- package/dist/builder-CdLE4J6O.mjs.map +1 -0
- package/dist/{builder-sSflu6nB.d.mts → builder-DgkqDylK.d.mts} +5 -6
- package/dist/{client-CadO5fAX.mjs → client-CILhdfsF.mjs} +104 -314
- package/dist/client-CILhdfsF.mjs.map +1 -0
- package/dist/client.d.mts +2 -2
- package/dist/client.mjs +2 -2
- package/dist/collections-DtwMc1NU.mjs +33 -0
- package/dist/collections-DtwMc1NU.mjs.map +1 -0
- package/dist/core.d.mts +9 -8
- package/dist/core.mjs +7 -7
- package/dist/{error-BW7re1UN.d.mts → error-BD_0QFKT.d.mts} +2 -2
- package/dist/{error-CR8Urc84.d.mts → error-BquXDmIN.d.mts} +4 -4
- package/dist/{error-DrEIM7FO.mjs → error-L_QdmSnV.mjs} +2 -2
- package/dist/{error-DrEIM7FO.mjs.map → error-L_QdmSnV.mjs.map} +1 -1
- package/dist/{error-B__OMBRY.mjs → error-gtJvV6YS.mjs} +2 -2
- package/dist/{error-B__OMBRY.mjs.map → error-gtJvV6YS.mjs.map} +1 -1
- package/dist/{event-fU3Y01PR.d.mts → event-DJeb_DIL.d.mts} +4 -2
- package/dist/{event-D8Nfoh4K.mjs → event-DinQMmJ1.mjs} +16 -10
- package/dist/event-DinQMmJ1.mjs.map +1 -0
- package/dist/{filter-C0IMByE3.d.mts → filter-jI_9htF7.d.mts} +2 -2
- package/dist/{gossip-DpIvkBuj.d.mts → gossip-C7HLW5kI.d.mts} +35 -26
- package/dist/{gossip-oloDMWEj.mjs → gossip-CcTp9VpU.mjs} +23 -33
- package/dist/gossip-CcTp9VpU.mjs.map +1 -0
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/{index-CvkvqB16.d.mts → index-DU9tRFTZ.d.mts} +7 -20
- package/dist/{index-q4MDce_r.d.mts → index-DiXwNn2M.d.mts} +6 -8
- package/dist/{index-CNl2q67H.d.mts → index-DocgD9K0.d.mts} +92 -167
- package/dist/{index-D1bqFHKB.d.mts → index-InQZpHyQ.d.mts} +18 -18
- package/dist/{index-MxC28S4P.d.mts → index-Tx15HGAD.d.mts} +26 -28
- package/dist/{index-lvXeER5t.d.mts → index-iLT8UzY3.d.mts} +19 -37
- package/dist/index.d.mts +23 -21
- package/dist/index.mjs +21 -19
- package/dist/{instance-DQE8BoWt.mjs → instance-BcKzxHIP.mjs} +26 -27
- package/dist/instance-BcKzxHIP.mjs.map +1 -0
- package/dist/{instance-CIca-ynn.d.mts → instance-DjsL8h84.d.mts} +5 -5
- package/dist/{key-Cv4_pN3x.d.mts → key-D_5OgGT7.d.mts} +11 -7
- package/dist/{key-Bps-LqX7.mjs → key-bshqfmZL.mjs} +13 -9
- package/dist/key-bshqfmZL.mjs.map +1 -0
- package/dist/{loaders-o6YXB_Ri.mjs → loaders-DD0XAGzw.mjs} +73 -82
- package/dist/loaders-DD0XAGzw.mjs.map +1 -0
- package/dist/loaders.d.mts +3 -3
- package/dist/loaders.mjs +2 -2
- package/dist/{memory-C2EVx0_O.mjs → memory-D_-5-T31.mjs} +2 -2
- package/dist/memory-D_-5-T31.mjs.map +1 -0
- package/dist/{memory-index-Do1mz0WF.mjs → memory-index-C6rujtoc.mjs} +30 -247
- package/dist/memory-index-C6rujtoc.mjs.map +1 -0
- package/dist/message-CWLcFTQ1.mjs +194 -0
- package/dist/message-CWLcFTQ1.mjs.map +1 -0
- package/dist/{message-iGPIqnn0.d.mts → message-CbBZOhq3.d.mts} +3 -3
- package/dist/{nip17-CKIufV98.d.mts → nip17-cTDFfFVt.d.mts} +5 -6
- package/dist/{nip19-BevCJZsp.d.mts → nip19-D1OnJ192.d.mts} +3 -9
- package/dist/{nip46-DDV4dC56.d.mts → nip46-DrsPI8EX.d.mts} +3 -5
- package/dist/nip51-DoPUvQ--.d.mts +87 -0
- package/dist/{nip59-zQgdLcEr.d.mts → nip59-BFIihLYY.d.mts} +3 -4
- package/dist/nip65-52HiwiyD.d.mts +23 -0
- package/dist/{nip77-DH4RhS5r.d.mts → nip77-C9QcZn7b.d.mts} +3 -4
- package/dist/nips/blossom.d.mts +12 -8
- package/dist/nips/blossom.mjs +30 -9
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.mjs +17 -7
- package/dist/nips/nip04.mjs.map +1 -1
- package/dist/nips/nip05.d.mts +2 -3
- package/dist/nips/nip05.mjs +1 -4
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +3 -3
- package/dist/nips/nip10.mjs +3 -3
- package/dist/nips/nip11.d.mts +1 -1
- package/dist/nips/nip11.mjs +4 -4
- package/dist/nips/nip11.mjs.map +1 -1
- package/dist/nips/nip13.d.mts +7 -7
- package/dist/nips/nip13.mjs +32 -10
- package/dist/nips/nip13.mjs.map +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +13 -33
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +2 -2
- package/dist/nips/nip19.mjs +16 -28
- package/dist/nips/nip19.mjs.map +1 -1
- package/dist/nips/nip21.d.mts +6 -1
- package/dist/nips/nip21.mjs +15 -5
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +4 -4
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.d.mts +1 -1
- package/dist/nips/nip42.mjs +2 -1
- package/dist/nips/nip42.mjs.map +1 -1
- package/dist/nips/nip44.mjs +1 -1
- package/dist/nips/nip46.d.mts +2 -2
- package/dist/nips/nip46.mjs +15 -19
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +5 -5
- package/dist/nips/nip49.mjs +4 -4
- package/dist/nips/nip49.mjs.map +1 -1
- package/dist/nips/nip51.d.mts +2 -86
- package/dist/nips/nip51.mjs +7 -23
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.d.mts +7 -5
- package/dist/nips/nip57.mjs +12 -17
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +16 -16
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +2 -2
- package/dist/nips/nip65.mjs +14 -37
- package/dist/nips/nip65.mjs.map +1 -1
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +33 -32
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +5 -5
- package/dist/nips/nip98.mjs +8 -8
- package/dist/nips/nip98.mjs.map +1 -1
- package/dist/{pool-DBIYMtwy.d.mts → pool-SzJKPIa3.d.mts} +96 -91
- package/dist/pool-YHRZgcQ3.mjs +596 -0
- package/dist/pool-YHRZgcQ3.mjs.map +1 -0
- package/dist/put-DS0yzVJx.mjs +187 -0
- package/dist/put-DS0yzVJx.mjs.map +1 -0
- package/dist/{reactive-7aqeip4A.d.mts → reactive-BJ1ggn4d.d.mts} +7 -22
- package/dist/{reactive-BfhPp9a3.mjs → reactive-BJRoVHxU.mjs} +80 -65
- package/dist/reactive-BJRoVHxU.mjs.map +1 -0
- package/dist/{pool-As20OGxB.mjs → relay-CvxPiQ_m.mjs} +178 -721
- package/dist/relay-CvxPiQ_m.mjs.map +1 -0
- package/dist/relay.d.mts +4 -4
- package/dist/relay.mjs +3 -2
- package/dist/serial-Bzpw-xz3.mjs +21 -0
- package/dist/serial-Bzpw-xz3.mjs.map +1 -0
- package/dist/{signer-CYQutDzD.mjs → signer-yW3slDC2.mjs} +12 -14
- package/dist/signer-yW3slDC2.mjs.map +1 -0
- package/dist/signer.d.mts +2 -2
- package/dist/signer.mjs +2 -2
- package/dist/{storage-Dky3wuoH.mjs → storage-CZ52ifcQ.mjs} +17 -42
- package/dist/storage-CZ52ifcQ.mjs.map +1 -0
- package/dist/storage.d.mts +3 -3
- package/dist/storage.mjs +4 -4
- package/dist/store.d.mts +1 -1
- package/dist/store.mjs +1 -1
- package/dist/testing.d.mts +18 -18
- package/dist/testing.mjs +9 -9
- package/dist/testing.mjs.map +1 -1
- package/dist/{types-2pCUprRU.d.mts → types-Cm-XMgtH.d.mts} +6 -6
- package/dist/{types-CRQEk0Oi.d.mts → types-DNmiscfd.d.mts} +2 -2
- package/dist/{util-LfQirgDk.mjs → util-yFYzlbrm.mjs} +29 -7
- package/dist/util-yFYzlbrm.mjs.map +1 -0
- package/dist/{verifier-t5WoWbtY.mjs → verifier-BTLOpo1n.mjs} +3 -3
- package/dist/verifier-BTLOpo1n.mjs.map +1 -0
- package/dist/verifier-CgTFpgsC.d.mts +22 -0
- package/dist/wasm.browser.d.mts +3 -3
- package/dist/wasm.browser.mjs +3 -3
- package/dist/wasm.d.mts +3 -3
- package/dist/wasm.mjs +3 -3
- package/dist/wasm.mjs.map +1 -1
- package/package.json +5 -9
- package/dist/abort-Ba8tDrOI.mjs.map +0 -1
- package/dist/builder-9tjI6WQ3.mjs.map +0 -1
- package/dist/client-CadO5fAX.mjs.map +0 -1
- package/dist/event-D8Nfoh4K.mjs.map +0 -1
- package/dist/gossip-oloDMWEj.mjs.map +0 -1
- package/dist/instance-DQE8BoWt.mjs.map +0 -1
- package/dist/key-Bps-LqX7.mjs.map +0 -1
- package/dist/loaders-o6YXB_Ri.mjs.map +0 -1
- package/dist/memory-C2EVx0_O.mjs.map +0 -1
- package/dist/memory-index-Do1mz0WF.mjs.map +0 -1
- package/dist/message-DcY6Fi9o.mjs +0 -193
- package/dist/message-DcY6Fi9o.mjs.map +0 -1
- package/dist/nip65-CjgmdpPB.d.mts +0 -26
- package/dist/nips/nip96.d.mts +0 -40
- package/dist/nips/nip96.mjs +0 -118
- package/dist/nips/nip96.mjs.map +0 -1
- package/dist/pool-As20OGxB.mjs.map +0 -1
- package/dist/reactive-BfhPp9a3.mjs.map +0 -1
- package/dist/signer-CYQutDzD.mjs.map +0 -1
- package/dist/storage-Dky3wuoH.mjs.map +0 -1
- package/dist/util-LfQirgDk.mjs.map +0 -1
- package/dist/verifier-t5WoWbtY.mjs.map +0 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { o as isHex32 } from "./util-yFYzlbrm.mjs";
|
|
2
|
+
import { i as isEphemeralKind, t as Kind } from "./kind-Cxwhhkx8.mjs";
|
|
3
|
+
import { a as isReplaceableWinner, g as eventAddress, m as validateSignedEvent, x as parseEventAddress } from "./event-DinQMmJ1.mjs";
|
|
4
|
+
//#region src/storage/deletion.ts
|
|
5
|
+
/**
|
|
6
|
+
* NIP-09 application plan for a kind 5 event.
|
|
7
|
+
*
|
|
8
|
+
* - `e` tags: delete only when the target exists, is not itself kind 5, and shares pubkey.
|
|
9
|
+
* - Unknown `e` ids are pending until the event arrives (client MUST validate pubkey).
|
|
10
|
+
* - `a` tags: tombstone that coordinate up to this deletion's `created_at` when pubkey matches.
|
|
11
|
+
* - Deleting a deletion request is a no-op.
|
|
12
|
+
*/
|
|
13
|
+
function planDeletion(deletion, getById) {
|
|
14
|
+
const { pubkey } = deletion;
|
|
15
|
+
const removeIds = [];
|
|
16
|
+
const pendingIds = [];
|
|
17
|
+
const coordinates = [];
|
|
18
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
19
|
+
const seenCoords = /* @__PURE__ */ new Set();
|
|
20
|
+
for (const tag of deletion.tags) {
|
|
21
|
+
if (tag[0] === "e" && tag[1] !== void 0 && isHex32(tag[1].toLowerCase())) {
|
|
22
|
+
const id = tag[1].toLowerCase();
|
|
23
|
+
if (seenIds.has(id)) continue;
|
|
24
|
+
seenIds.add(id);
|
|
25
|
+
const existing = getById(id);
|
|
26
|
+
if (!existing) {
|
|
27
|
+
pendingIds.push({
|
|
28
|
+
id,
|
|
29
|
+
pubkey
|
|
30
|
+
});
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (existing.kind === Kind.EventDeletion) continue;
|
|
34
|
+
if (existing.pubkey !== pubkey) continue;
|
|
35
|
+
removeIds.push(id);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (tag[0] === "a" && tag[1] !== void 0) {
|
|
39
|
+
const coord = parseEventAddress(tag[1]);
|
|
40
|
+
if (!coord || coord.pubkey !== pubkey) continue;
|
|
41
|
+
const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;
|
|
42
|
+
if (seenCoords.has(key)) continue;
|
|
43
|
+
seenCoords.add(key);
|
|
44
|
+
coordinates.push({
|
|
45
|
+
key,
|
|
46
|
+
until: deletion.created_at
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
removeIds,
|
|
52
|
+
pendingIds,
|
|
53
|
+
coordinates
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** In-memory NIP-09 tombstones shared by MemoryEventStore and IndexedDbEventStore. */
|
|
57
|
+
var DeletionState = class {
|
|
58
|
+
ids = /* @__PURE__ */ new Set();
|
|
59
|
+
pending = /* @__PURE__ */ new Map();
|
|
60
|
+
coordinates = /* @__PURE__ */ new Map();
|
|
61
|
+
clear() {
|
|
62
|
+
this.ids.clear();
|
|
63
|
+
this.pending.clear();
|
|
64
|
+
this.coordinates.clear();
|
|
65
|
+
}
|
|
66
|
+
covers(event) {
|
|
67
|
+
if (this.ids.has(event.id)) return true;
|
|
68
|
+
if (event.kind === Kind.EventDeletion) return false;
|
|
69
|
+
const pendingPk = this.pending.get(event.id);
|
|
70
|
+
if (pendingPk !== void 0 && event.pubkey === pendingPk) return true;
|
|
71
|
+
const addr = eventAddress(event);
|
|
72
|
+
if (addr === void 0) return false;
|
|
73
|
+
const until = this.coordinates.get(addr);
|
|
74
|
+
return until !== void 0 && event.created_at <= until;
|
|
75
|
+
}
|
|
76
|
+
absorb(plan) {
|
|
77
|
+
for (const id of plan.removeIds) {
|
|
78
|
+
this.ids.add(id);
|
|
79
|
+
this.pending.delete(id);
|
|
80
|
+
}
|
|
81
|
+
for (const p of plan.pendingIds) if (!this.ids.has(p.id)) this.pending.set(p.id, p.pubkey);
|
|
82
|
+
for (const c of plan.coordinates) {
|
|
83
|
+
const prev = this.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;
|
|
84
|
+
this.coordinates.delete(c.key);
|
|
85
|
+
this.coordinates.set(c.key, Math.max(prev, c.until));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function coordinateRemovals(coordinates, getCurrent) {
|
|
90
|
+
const ids = [];
|
|
91
|
+
for (const c of coordinates) {
|
|
92
|
+
const current = getCurrent(c.key);
|
|
93
|
+
if (current && current.created_at <= c.until) ids.push(current.id);
|
|
94
|
+
}
|
|
95
|
+
return ids;
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/storage/put.ts
|
|
99
|
+
function outboxBoundKey(pubkey, kind) {
|
|
100
|
+
return `${pubkey.toLowerCase()}:${kind}`;
|
|
101
|
+
}
|
|
102
|
+
function decidePut(raw, lookup) {
|
|
103
|
+
if (!validateSignedEvent(raw)) return {
|
|
104
|
+
action: "skip",
|
|
105
|
+
result: "invalid",
|
|
106
|
+
event: raw
|
|
107
|
+
};
|
|
108
|
+
const event = raw;
|
|
109
|
+
if (lookup.deletion.ids.has(event.id) || lookup.getById(event.id)) return {
|
|
110
|
+
action: "skip",
|
|
111
|
+
result: "duplicate",
|
|
112
|
+
event
|
|
113
|
+
};
|
|
114
|
+
if (event.kind === Kind.EventDeletion) {
|
|
115
|
+
const plan = planDeletion(event, lookup.getById);
|
|
116
|
+
return {
|
|
117
|
+
action: "delete",
|
|
118
|
+
result: "deleted",
|
|
119
|
+
event,
|
|
120
|
+
plan,
|
|
121
|
+
coordIds: coordinateRemovals(plan.coordinates, lookup.getReplaceable)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (lookup.deletion.covers(event)) return {
|
|
125
|
+
action: "tombstone",
|
|
126
|
+
result: "duplicate",
|
|
127
|
+
event
|
|
128
|
+
};
|
|
129
|
+
if (isEphemeralKind(event.kind)) return {
|
|
130
|
+
action: "skip",
|
|
131
|
+
result: "ephemeral",
|
|
132
|
+
event
|
|
133
|
+
};
|
|
134
|
+
const address = eventAddress(event);
|
|
135
|
+
if (address !== void 0) {
|
|
136
|
+
const prev = lookup.getReplaceable(address);
|
|
137
|
+
if (prev && prev.id !== event.id && !isReplaceableWinner(event, prev)) return {
|
|
138
|
+
action: "skip",
|
|
139
|
+
result: "rejected",
|
|
140
|
+
event
|
|
141
|
+
};
|
|
142
|
+
if (prev !== void 0 && prev.evicted !== true) return {
|
|
143
|
+
action: "insert",
|
|
144
|
+
result: "replaced",
|
|
145
|
+
event,
|
|
146
|
+
address,
|
|
147
|
+
replaceId: prev.id
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
action: "insert",
|
|
151
|
+
result: "accepted",
|
|
152
|
+
event,
|
|
153
|
+
address
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
action: "insert",
|
|
158
|
+
result: "accepted",
|
|
159
|
+
event
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function applyPutMemory(s, d) {
|
|
163
|
+
if (d.action === "skip") return d.result;
|
|
164
|
+
if (d.action === "tombstone") {
|
|
165
|
+
s.deletion.ids.add(d.event.id);
|
|
166
|
+
s.deletion.pending.delete(d.event.id);
|
|
167
|
+
return "duplicate";
|
|
168
|
+
}
|
|
169
|
+
if (d.action === "delete") {
|
|
170
|
+
s.deletion.pending.delete(d.event.id);
|
|
171
|
+
s.deletion.absorb(d.plan);
|
|
172
|
+
for (const id of d.plan.removeIds) s.indexRemove(id);
|
|
173
|
+
for (const id of d.coordIds) {
|
|
174
|
+
s.deletion.ids.add(id);
|
|
175
|
+
s.indexRemove(id);
|
|
176
|
+
}
|
|
177
|
+
s.indexInsert(d.event);
|
|
178
|
+
return "deleted";
|
|
179
|
+
}
|
|
180
|
+
if (d.replaceId !== void 0) s.indexRemove(d.replaceId);
|
|
181
|
+
s.indexInsert(d.event);
|
|
182
|
+
return d.result;
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
export { planDeletion as a, DeletionState as i, decidePut as n, outboxBoundKey as r, applyPutMemory as t };
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=put-DS0yzVJx.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"put-DS0yzVJx.mjs","names":[],"sources":["../src/storage/deletion.ts","../src/storage/put.ts"],"sourcesContent":["import type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { eventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { isHex32 } from \"../core/util.ts\";\n\nexport type DeletionPlan = {\n /** Stored events validated as same-pubkey (not kind 5) and to be removed now. */\n removeIds: string[];\n /** Referenced ids not in the store; remember deletion pubkey until the event arrives. */\n pendingIds: Array<{ id: string; pubkey: string }>;\n /** Replaceable/addressable coordinates tombstoned up to `until` (deletion created_at). */\n coordinates: Array<{ key: string; until: number }>;\n};\n\n/**\n * NIP-09 application plan for a kind 5 event.\n *\n * - `e` tags: delete only when the target exists, is not itself kind 5, and shares pubkey.\n * - Unknown `e` ids are pending until the event arrives (client MUST validate pubkey).\n * - `a` tags: tombstone that coordinate up to this deletion's `created_at` when pubkey matches.\n * - Deleting a deletion request is a no-op.\n */\nexport function planDeletion(\n deletion: Pick<Event, \"pubkey\" | \"created_at\" | \"tags\">,\n getById: (id: string) => Pick<Event, \"id\" | \"pubkey\" | \"kind\"> | undefined,\n): DeletionPlan {\n const { pubkey } = deletion;\n const removeIds: string[] = [];\n const pendingIds: Array<{ id: string; pubkey: string }> = [];\n const coordinates: Array<{ key: string; until: number }> = [];\n const seenIds = new Set<string>();\n const seenCoords = new Set<string>();\n\n for (const tag of deletion.tags) {\n if (tag[0] === \"e\" && tag[1] !== undefined && isHex32(tag[1].toLowerCase())) {\n const id = tag[1].toLowerCase();\n if (seenIds.has(id)) {\n continue;\n }\n seenIds.add(id);\n const existing = getById(id);\n if (!existing) {\n pendingIds.push({ id, pubkey });\n continue;\n }\n if (existing.kind === Kind.EventDeletion) {\n continue;\n }\n if (existing.pubkey !== pubkey) {\n continue;\n }\n removeIds.push(id);\n continue;\n }\n\n if (tag[0] === \"a\" && tag[1] !== undefined) {\n const coord = parseEventAddress(tag[1]);\n if (!coord || coord.pubkey !== pubkey) {\n continue;\n }\n const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;\n if (seenCoords.has(key)) {\n continue;\n }\n seenCoords.add(key);\n coordinates.push({ key, until: deletion.created_at });\n }\n }\n\n return { removeIds, pendingIds, coordinates };\n}\n\n/** In-memory NIP-09 tombstones shared by MemoryEventStore and IndexedDbEventStore. */\nexport class DeletionState {\n readonly ids: Set<string> = new Set<string>();\n readonly pending: Map<string, string> = new Map<string, string>();\n readonly coordinates: Map<string, number> = new Map<string, number>();\n\n clear(): void {\n this.ids.clear();\n this.pending.clear();\n this.coordinates.clear();\n }\n\n covers(event: Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\">): boolean {\n if (this.ids.has(event.id)) {\n return true;\n }\n if (event.kind === Kind.EventDeletion) {\n return false;\n }\n const pendingPk = this.pending.get(event.id);\n if (pendingPk !== undefined && event.pubkey === pendingPk) {\n return true;\n }\n const addr = eventAddress(event);\n if (addr === undefined) {\n return false;\n }\n const until = this.coordinates.get(addr);\n return until !== undefined && event.created_at <= until;\n }\n\n absorb(plan: DeletionPlan): void {\n for (const id of plan.removeIds) {\n this.ids.add(id);\n this.pending.delete(id);\n }\n for (const p of plan.pendingIds) {\n if (!this.ids.has(p.id)) {\n this.pending.set(p.id, p.pubkey);\n }\n }\n for (const c of plan.coordinates) {\n const prev = this.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;\n // Re-absorbing a coordinate moves it to the newest end so FIFO\n // tombstone caps evict the oldest marker, not the freshest.\n this.coordinates.delete(c.key);\n this.coordinates.set(c.key, Math.max(prev, c.until));\n }\n }\n}\n\nexport function coordinateRemovals(\n coordinates: ReadonlyArray<{ key: string; until: number }>,\n getCurrent: (key: string) => Pick<Event, \"id\" | \"created_at\"> | undefined,\n): string[] {\n const ids: string[] = [];\n for (const c of coordinates) {\n const current = getCurrent(c.key);\n if (current && current.created_at <= c.until) {\n ids.push(current.id);\n }\n }\n return ids;\n}\n","import type { Event } from \"../core/event.ts\";\nimport { isReplaceableWinner, validateSignedEvent } from \"../core/event.ts\";\nimport { isEphemeralKind, Kind } from \"../core/kind.ts\";\nimport { eventAddress } from \"../core/tag.ts\";\nimport { coordinateRemovals, planDeletion } from \"./deletion.ts\";\nimport type { DeletionPlan, DeletionState } from \"./deletion.ts\";\nimport type { PutResult } from \"./types.ts\";\n\nexport function outboxBoundKey(pubkey: string, kind: number): string {\n return `${pubkey.toLowerCase()}:${kind}`;\n}\n\nexport type PutDecision =\n | { action: \"skip\"; result: \"duplicate\" | \"ephemeral\" | \"rejected\" | \"invalid\"; event: Event }\n | { action: \"tombstone\"; result: \"duplicate\"; event: Event }\n | { action: \"delete\"; result: \"deleted\"; event: Event; plan: DeletionPlan; coordIds: string[] }\n | {\n action: \"insert\";\n result: \"accepted\" | \"replaced\";\n event: Event;\n address?: string;\n replaceId?: string;\n };\n\nexport type PutLookup = {\n deletion: DeletionState;\n getById: (\n id: string,\n ) => Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\"> | undefined;\n /**\n * Current replaceable winner for an address. `evicted: true` marks an eviction watermark: the\n * winning event body was dropped, but its id/created_at still reject older versions, and a re-put\n * of the watermarked id re-inserts it.\n */\n getReplaceable: (\n address: string,\n ) => (Pick<Event, \"id\" | \"created_at\"> & { evicted?: boolean }) | undefined;\n};\n\nexport function decidePut(raw: Event, lookup: PutLookup): PutDecision {\n // Canonical-input precondition: non-canonical events are never indexed.\n if (!validateSignedEvent(raw)) {\n return { action: \"skip\", result: \"invalid\", event: raw };\n }\n const event = raw;\n if (lookup.deletion.ids.has(event.id) || lookup.getById(event.id)) {\n return { action: \"skip\", result: \"duplicate\", event };\n }\n if (event.kind === Kind.EventDeletion) {\n const plan = planDeletion(event, lookup.getById);\n const coordIds = coordinateRemovals(plan.coordinates, lookup.getReplaceable);\n return { action: \"delete\", result: \"deleted\", event, plan, coordIds };\n }\n if (lookup.deletion.covers(event)) {\n return { action: \"tombstone\", result: \"duplicate\", event };\n }\n if (isEphemeralKind(event.kind)) {\n return { action: \"skip\", result: \"ephemeral\", event };\n }\n const address = eventAddress(event);\n if (address !== undefined) {\n const prev = lookup.getReplaceable(address);\n // A stored incumbent rejects stale versions; an eviction watermark does\n // the same, except a re-put of the watermarked id re-inserts it.\n if (prev && prev.id !== event.id && !isReplaceableWinner(event, prev)) {\n return { action: \"skip\", result: \"rejected\", event };\n }\n if (prev !== undefined && prev.evicted !== true) {\n return { action: \"insert\", result: \"replaced\", event, address, replaceId: prev.id };\n }\n return { action: \"insert\", result: \"accepted\", event, address };\n }\n return { action: \"insert\", result: \"accepted\", event };\n}\n\nexport function applyPutMemory(\n s: {\n deletion: DeletionState;\n indexInsert: (event: Event) => void;\n indexRemove: (id: string) => boolean;\n },\n d: PutDecision,\n): PutResult {\n if (d.action === \"skip\") {\n return d.result;\n }\n if (d.action === \"tombstone\") {\n s.deletion.ids.add(d.event.id);\n s.deletion.pending.delete(d.event.id);\n return \"duplicate\";\n }\n if (d.action === \"delete\") {\n s.deletion.pending.delete(d.event.id);\n s.deletion.absorb(d.plan);\n for (const id of d.plan.removeIds) {\n s.indexRemove(id);\n }\n for (const id of d.coordIds) {\n s.deletion.ids.add(id);\n s.indexRemove(id);\n }\n s.indexInsert(d.event);\n return \"deleted\";\n }\n if (d.replaceId !== undefined) {\n s.indexRemove(d.replaceId);\n }\n s.indexInsert(d.event);\n return d.result;\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as Event } from "./event-
|
|
2
|
-
import { t as Filter } from "./filter-
|
|
3
|
-
import { i as PutResult } from "./types-
|
|
1
|
+
import { t as Event } from "./event-DJeb_DIL.mjs";
|
|
2
|
+
import { t as Filter } from "./filter-jI_9htF7.mjs";
|
|
3
|
+
import { i as PutResult } from "./types-Cm-XMgtH.mjs";
|
|
4
4
|
//#region src/store/reactive.d.ts
|
|
5
5
|
/** A reactive snapshot source compatible with `useSyncExternalStore`. */
|
|
6
6
|
type Watch<T> = {
|
|
@@ -10,23 +10,11 @@ type Watch<T> = {
|
|
|
10
10
|
/** Capacity options for {@link ReactiveEventStore}. */
|
|
11
11
|
type ReactiveEventStoreOptions = {
|
|
12
12
|
/** Max stored events before LRU eviction. Default 50_000. */
|
|
13
|
-
maxEvents?: number;
|
|
13
|
+
maxEvents?: number | undefined;
|
|
14
14
|
/** Max ids tracked by `seenOn`. Default 20_000. */
|
|
15
|
-
maxSeenOnEntries?: number;
|
|
15
|
+
maxSeenOnEntries?: number | undefined;
|
|
16
16
|
/** FIFO cap on tombstoned deletion ids. Default 100_000. */
|
|
17
|
-
maxTombstones?: number;
|
|
18
|
-
};
|
|
19
|
-
/** Category of a {@link Watch} subscription. */
|
|
20
|
-
type WatchKind = "event" | "replaceable" | "query";
|
|
21
|
-
/** Structural handle the store needs for invalidation; implemented by WatchImpl. */
|
|
22
|
-
type WatchHandle = {
|
|
23
|
-
readonly kind: WatchKind;
|
|
24
|
-
readonly key: string;
|
|
25
|
-
readonly filters: ReadonlyArray<Filter> | undefined;
|
|
26
|
-
readonly subscribed: boolean;
|
|
27
|
-
readonly pinnedIds: ReadonlyArray<string>;
|
|
28
|
-
_markDirty: () => void;
|
|
29
|
-
_notify: () => void;
|
|
17
|
+
maxTombstones?: number | undefined;
|
|
30
18
|
};
|
|
31
19
|
/**
|
|
32
20
|
* Synchronous reactive in-memory event store backed by {@link MemoryIndex}. The single read source
|
|
@@ -36,7 +24,6 @@ type WatchHandle = {
|
|
|
36
24
|
*/
|
|
37
25
|
declare class ReactiveEventStore {
|
|
38
26
|
#private;
|
|
39
|
-
_version: number;
|
|
40
27
|
constructor(opts?: ReactiveEventStoreOptions);
|
|
41
28
|
add(event: Event, relayUrl?: string): PutResult;
|
|
42
29
|
addMany(events: ReadonlyArray<Event>, relayUrl?: string): PutResult[];
|
|
@@ -60,9 +47,7 @@ declare class ReactiveEventStore {
|
|
|
60
47
|
onInsert(listener: (event: Event) => void): () => void;
|
|
61
48
|
/** Synchronous listener for every physical index remove (after watch invalidation). */
|
|
62
49
|
onRemove(listener: (event: Event) => void): () => void;
|
|
63
|
-
_register(watch: WatchHandle): void;
|
|
64
|
-
_unregister(watch: WatchHandle): void;
|
|
65
50
|
}
|
|
66
51
|
//#endregion
|
|
67
52
|
export { ReactiveEventStoreOptions as n, Watch as r, ReactiveEventStore as t };
|
|
68
|
-
//# sourceMappingURL=reactive-
|
|
53
|
+
//# sourceMappingURL=reactive-BJ1ggn4d.d.mts.map
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { u as normalizeURL } from "./util-yFYzlbrm.mjs";
|
|
2
|
+
import { t as Kind } from "./kind-Cxwhhkx8.mjs";
|
|
3
|
+
import { g as eventAddress, o as itemCompare, v as formatEventAddress, x as parseEventAddress } from "./event-DinQMmJ1.mjs";
|
|
3
4
|
import { o as matchFilters, r as filterFingerprint } from "./filter-BQl3GlHt.mjs";
|
|
4
5
|
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
5
|
-
import { t as
|
|
6
|
+
import { i as trimOldest, n as removeFromSetMap, t as addToSetMap } from "./collections-DtwMc1NU.mjs";
|
|
7
|
+
import { t as MemoryIndex } from "./memory-index-C6rujtoc.mjs";
|
|
6
8
|
//#region src/store/reactive.ts
|
|
7
9
|
const SEEN_ON_PER_ID = 16;
|
|
8
10
|
var WatchImpl = class {
|
|
9
|
-
|
|
11
|
+
host;
|
|
10
12
|
kind;
|
|
11
13
|
key;
|
|
12
14
|
filters;
|
|
@@ -18,8 +20,8 @@ var WatchImpl = class {
|
|
|
18
20
|
#version = -1;
|
|
19
21
|
#dirty = false;
|
|
20
22
|
#pendingRemove = false;
|
|
21
|
-
constructor(
|
|
22
|
-
this.
|
|
23
|
+
constructor(host, kind, key, compute, equal, snapshotIds, filters) {
|
|
24
|
+
this.host = host;
|
|
23
25
|
this.kind = kind;
|
|
24
26
|
this.key = key;
|
|
25
27
|
this.filters = filters;
|
|
@@ -27,7 +29,7 @@ var WatchImpl = class {
|
|
|
27
29
|
this.equal = equal;
|
|
28
30
|
this.snapshotIds = snapshotIds;
|
|
29
31
|
this.#snapshot = compute();
|
|
30
|
-
this.#version =
|
|
32
|
+
this.#version = host.version();
|
|
31
33
|
}
|
|
32
34
|
get subscribed() {
|
|
33
35
|
return this.#subscribers.size > 0;
|
|
@@ -39,30 +41,33 @@ var WatchImpl = class {
|
|
|
39
41
|
const wasUnsubscribed = this.#subscribers.size === 0;
|
|
40
42
|
this.#subscribers.add(onChange);
|
|
41
43
|
this.#pendingRemove = false;
|
|
42
|
-
if (wasUnsubscribed && this.#version !== this.
|
|
43
|
-
this.
|
|
44
|
+
if (wasUnsubscribed && this.#version !== this.host.version()) this.#dirty = true;
|
|
45
|
+
this.host.register(this);
|
|
44
46
|
return () => {
|
|
45
47
|
this.#subscribers.delete(onChange);
|
|
46
48
|
if (this.#subscribers.size > 0 || this.#pendingRemove) return;
|
|
47
49
|
this.#pendingRemove = true;
|
|
48
50
|
queueMicrotask(() => {
|
|
49
|
-
if (this.#pendingRemove) this.
|
|
51
|
+
if (this.#pendingRemove) this.host.unregister(this);
|
|
50
52
|
});
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
55
|
getSnapshot() {
|
|
54
|
-
if (this.subscribed ? this.#dirty : this.#version !== this.
|
|
56
|
+
if (this.subscribed ? this.#dirty : this.#version !== this.host.version()) {
|
|
55
57
|
const next = this.compute();
|
|
56
|
-
if (!this.equal(next, this.#snapshot))
|
|
57
|
-
|
|
58
|
+
if (!this.equal(next, this.#snapshot)) {
|
|
59
|
+
this.#snapshot = next;
|
|
60
|
+
this.host.repin(this);
|
|
61
|
+
}
|
|
62
|
+
this.#version = this.host.version();
|
|
58
63
|
this.#dirty = false;
|
|
59
64
|
}
|
|
60
65
|
return this.#snapshot;
|
|
61
66
|
}
|
|
62
|
-
|
|
67
|
+
markDirty() {
|
|
63
68
|
this.#dirty = true;
|
|
64
69
|
}
|
|
65
|
-
|
|
70
|
+
notify() {
|
|
66
71
|
for (const onChange of this.#subscribers) invokeSafely(onChange);
|
|
67
72
|
}
|
|
68
73
|
};
|
|
@@ -86,7 +91,7 @@ var ReactiveEventStore = class {
|
|
|
86
91
|
#index;
|
|
87
92
|
#maxEvents;
|
|
88
93
|
#maxSeenOnEntries;
|
|
89
|
-
|
|
94
|
+
#version = 0;
|
|
90
95
|
#recency = /* @__PURE__ */ new Set();
|
|
91
96
|
#seenOn = /* @__PURE__ */ new Map();
|
|
92
97
|
#idWatches = /* @__PURE__ */ new Map();
|
|
@@ -95,10 +100,40 @@ var ReactiveEventStore = class {
|
|
|
95
100
|
#addressCache = /* @__PURE__ */ new Map();
|
|
96
101
|
#queryCache = /* @__PURE__ */ new Map();
|
|
97
102
|
#queryRegistry = /* @__PURE__ */ new Set();
|
|
103
|
+
#pinned = /* @__PURE__ */ new Map();
|
|
104
|
+
#watchPins = /* @__PURE__ */ new Map();
|
|
98
105
|
#dirty = /* @__PURE__ */ new Set();
|
|
99
106
|
#flushScheduled = false;
|
|
100
107
|
#insertListeners = /* @__PURE__ */ new Set();
|
|
101
108
|
#removeListeners = /* @__PURE__ */ new Set();
|
|
109
|
+
#watchHost = {
|
|
110
|
+
version: () => this.#version,
|
|
111
|
+
register: (watch) => {
|
|
112
|
+
this.#registerWatch(watch);
|
|
113
|
+
if (!this.#watchPins.has(watch)) {
|
|
114
|
+
const ids = watch.pinnedIds;
|
|
115
|
+
this.#watchPins.set(watch, ids);
|
|
116
|
+
this.#pin(ids);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
unregister: (watch) => {
|
|
120
|
+
if (watch.subscribed) return;
|
|
121
|
+
this.#unregisterWatch(watch);
|
|
122
|
+
const ids = this.#watchPins.get(watch);
|
|
123
|
+
if (ids !== void 0) {
|
|
124
|
+
this.#watchPins.delete(watch);
|
|
125
|
+
this.#unpin(ids);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
repin: (watch) => {
|
|
129
|
+
const old = this.#watchPins.get(watch);
|
|
130
|
+
if (old === void 0) return;
|
|
131
|
+
const next = watch.pinnedIds;
|
|
132
|
+
this.#unpin(old);
|
|
133
|
+
this.#pin(next);
|
|
134
|
+
this.#watchPins.set(watch, next);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
102
137
|
constructor(opts) {
|
|
103
138
|
this.#maxEvents = opts?.maxEvents ?? 5e4;
|
|
104
139
|
this.#maxSeenOnEntries = opts?.maxSeenOnEntries ?? 2e4;
|
|
@@ -141,7 +176,7 @@ var ReactiveEventStore = class {
|
|
|
141
176
|
this.#index.clear();
|
|
142
177
|
this.#seenOn.clear();
|
|
143
178
|
this.#recency.clear();
|
|
144
|
-
this
|
|
179
|
+
this.#version += 1;
|
|
145
180
|
this.#invalidateAll();
|
|
146
181
|
}
|
|
147
182
|
get(id) {
|
|
@@ -175,7 +210,7 @@ var ReactiveEventStore = class {
|
|
|
175
210
|
const key = id.toLowerCase();
|
|
176
211
|
let watch = this.#eventCache.get(key);
|
|
177
212
|
if (!watch) {
|
|
178
|
-
watch = new WatchImpl(this, "event", key, () => this.get(key), sameRef, EVENT_IDS);
|
|
213
|
+
watch = new WatchImpl(this.#watchHost, "event", key, () => this.get(key), sameRef, EVENT_IDS);
|
|
179
214
|
this.#eventCache.set(key, watch);
|
|
180
215
|
}
|
|
181
216
|
return watch;
|
|
@@ -184,7 +219,7 @@ var ReactiveEventStore = class {
|
|
|
184
219
|
const address = formatEventAddress(kind, pubkey, d ?? "");
|
|
185
220
|
let watch = this.#addressCache.get(address);
|
|
186
221
|
if (!watch) {
|
|
187
|
-
watch = new WatchImpl(this, "replaceable", address, () => this.getByAddress(address), sameRef, EVENT_IDS);
|
|
222
|
+
watch = new WatchImpl(this.#watchHost, "replaceable", address, () => this.getByAddress(address), sameRef, EVENT_IDS);
|
|
188
223
|
this.#addressCache.set(address, watch);
|
|
189
224
|
}
|
|
190
225
|
return watch;
|
|
@@ -194,7 +229,7 @@ var ReactiveEventStore = class {
|
|
|
194
229
|
let watch = this.#queryCache.get(key);
|
|
195
230
|
if (!watch) {
|
|
196
231
|
const list = [...filters];
|
|
197
|
-
watch = new WatchImpl(this, "query", key, () => this.query(list), sameList, LIST_IDS, list);
|
|
232
|
+
watch = new WatchImpl(this.#watchHost, "query", key, () => this.query(list), sameList, LIST_IDS, list);
|
|
198
233
|
this.#queryCache.set(key, watch);
|
|
199
234
|
}
|
|
200
235
|
return watch;
|
|
@@ -213,26 +248,25 @@ var ReactiveEventStore = class {
|
|
|
213
248
|
this.#removeListeners.delete(listener);
|
|
214
249
|
};
|
|
215
250
|
}
|
|
216
|
-
|
|
251
|
+
#registerWatch(watch) {
|
|
217
252
|
switch (watch.kind) {
|
|
218
253
|
case "event":
|
|
219
|
-
|
|
254
|
+
addToSetMap(this.#idWatches, watch.key, watch);
|
|
220
255
|
return;
|
|
221
256
|
case "replaceable":
|
|
222
|
-
|
|
257
|
+
addToSetMap(this.#addressWatches, watch.key, watch);
|
|
223
258
|
return;
|
|
224
259
|
case "query": this.#queryRegistry.add(watch);
|
|
225
260
|
}
|
|
226
261
|
}
|
|
227
|
-
|
|
228
|
-
if (watch.subscribed) return;
|
|
262
|
+
#unregisterWatch(watch) {
|
|
229
263
|
switch (watch.kind) {
|
|
230
264
|
case "event":
|
|
231
|
-
|
|
265
|
+
removeFromSetMap(this.#idWatches, watch.key, watch);
|
|
232
266
|
if (this.#eventCache.get(watch.key) === watch) this.#eventCache.delete(watch.key);
|
|
233
267
|
return;
|
|
234
268
|
case "replaceable":
|
|
235
|
-
|
|
269
|
+
removeFromSetMap(this.#addressWatches, watch.key, watch);
|
|
236
270
|
if (this.#addressCache.get(watch.key) === watch) this.#addressCache.delete(watch.key);
|
|
237
271
|
return;
|
|
238
272
|
case "query":
|
|
@@ -241,8 +275,8 @@ var ReactiveEventStore = class {
|
|
|
241
275
|
}
|
|
242
276
|
}
|
|
243
277
|
#touch(id) {
|
|
244
|
-
|
|
245
|
-
|
|
278
|
+
this.#recency.delete(id);
|
|
279
|
+
this.#recency.add(id);
|
|
246
280
|
}
|
|
247
281
|
#recordSeen(id, relayUrl) {
|
|
248
282
|
const url = normalizeURL(relayUrl);
|
|
@@ -252,20 +286,16 @@ var ReactiveEventStore = class {
|
|
|
252
286
|
this.#seenOn.set(id, urls);
|
|
253
287
|
}
|
|
254
288
|
if (!urls.includes(url) && urls.length < SEEN_ON_PER_ID) urls.push(url);
|
|
255
|
-
|
|
256
|
-
const oldest = this.#seenOn.keys().next().value;
|
|
257
|
-
if (oldest === void 0) break;
|
|
258
|
-
this.#seenOn.delete(oldest);
|
|
259
|
-
}
|
|
289
|
+
trimOldest(this.#seenOn, this.#maxSeenOnEntries);
|
|
260
290
|
}
|
|
261
291
|
#onInsert(event) {
|
|
262
|
-
this
|
|
292
|
+
this.#version += 1;
|
|
263
293
|
this.#touch(event.id);
|
|
264
294
|
this.#invalidateByEvent(event);
|
|
265
295
|
for (const listener of this.#insertListeners) invokeSafely(() => listener(event));
|
|
266
296
|
}
|
|
267
297
|
#onRemove(event) {
|
|
268
|
-
this
|
|
298
|
+
this.#version += 1;
|
|
269
299
|
this.#recency.delete(event.id);
|
|
270
300
|
this.#invalidateByEvent(event);
|
|
271
301
|
for (const listener of this.#removeListeners) invokeSafely(() => listener(event));
|
|
@@ -278,7 +308,7 @@ var ReactiveEventStore = class {
|
|
|
278
308
|
const byAddress = this.#addressWatches.get(address);
|
|
279
309
|
if (byAddress) for (const watch of byAddress) this.#markDirty(watch);
|
|
280
310
|
}
|
|
281
|
-
if (event.kind ===
|
|
311
|
+
if (event.kind === Kind.EventDeletion) {
|
|
282
312
|
for (const tag of event.tags) if (tag[0] === "e" && tag[1] !== void 0) {
|
|
283
313
|
const watches = this.#idWatches.get(tag[1].toLowerCase());
|
|
284
314
|
if (watches) for (const watch of watches) this.#markDirty(watch);
|
|
@@ -298,7 +328,7 @@ var ReactiveEventStore = class {
|
|
|
298
328
|
this.#scheduleFlush();
|
|
299
329
|
}
|
|
300
330
|
#markDirty(watch) {
|
|
301
|
-
watch.
|
|
331
|
+
watch.markDirty();
|
|
302
332
|
this.#dirty.add(watch);
|
|
303
333
|
this.#scheduleFlush();
|
|
304
334
|
}
|
|
@@ -309,28 +339,27 @@ var ReactiveEventStore = class {
|
|
|
309
339
|
while (this.#dirty.size > 0) {
|
|
310
340
|
const batch = [...this.#dirty];
|
|
311
341
|
this.#dirty.clear();
|
|
312
|
-
for (const watch of batch) watch.
|
|
342
|
+
for (const watch of batch) watch.notify();
|
|
313
343
|
}
|
|
314
344
|
this.#flushScheduled = false;
|
|
315
345
|
});
|
|
316
346
|
}
|
|
317
|
-
#
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
347
|
+
#pin(ids) {
|
|
348
|
+
for (const id of ids) this.#pinned.set(id, (this.#pinned.get(id) ?? 0) + 1);
|
|
349
|
+
}
|
|
350
|
+
#unpin(ids) {
|
|
351
|
+
for (const id of ids) {
|
|
352
|
+
const count = (this.#pinned.get(id) ?? 0) - 1;
|
|
353
|
+
if (count <= 0) this.#pinned.delete(id);
|
|
354
|
+
else this.#pinned.set(id, count);
|
|
355
|
+
}
|
|
326
356
|
}
|
|
327
357
|
#evictIfNeeded(protectedId) {
|
|
328
358
|
if (this.#index.size <= this.#maxEvents) return;
|
|
329
|
-
const pinned = this.#pinnedIds();
|
|
330
359
|
const evicting = [];
|
|
331
360
|
for (const id of this.#recency) {
|
|
332
361
|
if (this.#index.size - evicting.length <= this.#maxEvents) break;
|
|
333
|
-
if (pinned.has(id) || id === protectedId) continue;
|
|
362
|
+
if (this.#pinned.has(id) || id === protectedId) continue;
|
|
334
363
|
if (this.#index.get(id) === void 0) continue;
|
|
335
364
|
evicting.push(id);
|
|
336
365
|
}
|
|
@@ -338,21 +367,7 @@ var ReactiveEventStore = class {
|
|
|
338
367
|
for (const id of evicting) this.#recency.delete(id);
|
|
339
368
|
}
|
|
340
369
|
};
|
|
341
|
-
function addWatch(map, key, watch) {
|
|
342
|
-
let set = map.get(key);
|
|
343
|
-
if (!set) {
|
|
344
|
-
set = /* @__PURE__ */ new Set();
|
|
345
|
-
map.set(key, set);
|
|
346
|
-
}
|
|
347
|
-
set.add(watch);
|
|
348
|
-
}
|
|
349
|
-
function removeWatch(map, key, watch) {
|
|
350
|
-
const set = map.get(key);
|
|
351
|
-
if (!set) return;
|
|
352
|
-
set.delete(watch);
|
|
353
|
-
if (set.size === 0) map.delete(key);
|
|
354
|
-
}
|
|
355
370
|
//#endregion
|
|
356
371
|
export { ReactiveEventStore as t };
|
|
357
372
|
|
|
358
|
-
//# sourceMappingURL=reactive-
|
|
373
|
+
//# sourceMappingURL=reactive-BJRoVHxU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reactive-BJRoVHxU.mjs","names":[],"sources":["../src/store/reactive.ts"],"sourcesContent":["import { addToSetMap, removeFromSetMap, trimOldest } from \"../core/collections.ts\";\nimport { itemCompare } from \"../core/event.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { filterFingerprint, matchFilters } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { invokeSafely } from \"../core/report.ts\";\nimport { formatEventAddress, eventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport { MemoryIndex } from \"../storage/memory-index.ts\";\nimport type { PutResult } from \"../storage/types.ts\";\n\n/** A reactive snapshot source compatible with `useSyncExternalStore`. */\nexport type Watch<T> = {\n subscribe: (onChange: () => void) => () => void;\n getSnapshot: () => T;\n};\n\n/** Capacity options for {@link ReactiveEventStore}. */\nexport type ReactiveEventStoreOptions = {\n /** Max stored events before LRU eviction. Default 50_000. */\n maxEvents?: number | undefined;\n /** Max ids tracked by `seenOn`. Default 20_000. */\n maxSeenOnEntries?: number | undefined;\n /** FIFO cap on tombstoned deletion ids. Default 100_000. */\n maxTombstones?: number | undefined;\n};\n\nconst SEEN_ON_PER_ID = 16;\n\n/** Category of a {@link Watch} subscription. */\ntype WatchKind = \"event\" | \"replaceable\" | \"query\";\n\n/** Structural handle the store needs for invalidation; implemented by WatchImpl. */\ntype WatchHandle = {\n readonly kind: WatchKind;\n readonly key: string;\n readonly filters: ReadonlyArray<Filter> | undefined;\n readonly subscribed: boolean;\n readonly pinnedIds: ReadonlyArray<string>;\n markDirty: () => void;\n notify: () => void;\n};\n\n/** Store internals exposed to a {@link WatchImpl}: version clock and registry/pin bookkeeping. */\ntype WatchHost = {\n version: () => number;\n register: (watch: WatchHandle) => void;\n unregister: (watch: WatchHandle) => void;\n /** Refresh pinned ids after a registered watch's snapshot changed. No-op when unregistered. */\n repin: (watch: WatchHandle) => void;\n};\n\nclass WatchImpl<T> implements Watch<T>, WatchHandle {\n readonly host: WatchHost;\n readonly kind: WatchKind;\n readonly key: string;\n readonly filters: ReadonlyArray<Filter> | undefined;\n readonly compute: () => T;\n readonly equal: (a: T, b: T) => boolean;\n readonly snapshotIds: (snapshot: T) => ReadonlyArray<string>;\n readonly #subscribers = new Set<() => void>();\n #snapshot: T;\n #version = -1;\n #dirty = false;\n #pendingRemove = false;\n\n constructor(\n host: WatchHost,\n kind: WatchKind,\n key: string,\n compute: () => T,\n equal: (a: T, b: T) => boolean,\n snapshotIds: (snapshot: T) => ReadonlyArray<string>,\n filters?: ReadonlyArray<Filter>,\n ) {\n this.host = host;\n this.kind = kind;\n this.key = key;\n this.filters = filters;\n this.compute = compute;\n this.equal = equal;\n this.snapshotIds = snapshotIds;\n this.#snapshot = compute();\n this.#version = host.version();\n }\n\n get subscribed(): boolean {\n return this.#subscribers.size > 0;\n }\n\n get pinnedIds(): ReadonlyArray<string> {\n return this.snapshotIds(this.#snapshot);\n }\n\n subscribe(onChange: () => void): () => void {\n const wasUnsubscribed = this.#subscribers.size === 0;\n this.#subscribers.add(onChange);\n this.#pendingRemove = false;\n // Writes that landed while unregistered must not be lost.\n if (wasUnsubscribed && this.#version !== this.host.version()) {\n this.#dirty = true;\n }\n this.host.register(this);\n return () => {\n this.#subscribers.delete(onChange);\n if (this.#subscribers.size > 0 || this.#pendingRemove) {\n return;\n }\n this.#pendingRemove = true;\n queueMicrotask(() => {\n if (this.#pendingRemove) {\n this.host.unregister(this);\n }\n });\n };\n }\n\n getSnapshot(): T {\n if (this.subscribed ? this.#dirty : this.#version !== this.host.version()) {\n const next = this.compute();\n if (!this.equal(next, this.#snapshot)) {\n this.#snapshot = next;\n this.host.repin(this);\n }\n this.#version = this.host.version();\n this.#dirty = false;\n }\n return this.#snapshot;\n }\n\n markDirty(): void {\n this.#dirty = true;\n }\n\n notify(): void {\n // Do not sync #version here: subscribers will call getSnapshot, which\n // recomputes against the bumped store version and keeps the reference\n // when the result is element-wise identical. A throwing subscriber is\n // reported and never aborts the remaining notifications.\n for (const onChange of this.#subscribers) {\n invokeSafely(onChange);\n }\n }\n}\n\nconst EVENT_IDS = (e: Event | undefined): ReadonlyArray<string> => (e ? [e.id] : []);\nconst LIST_IDS = (events: ReadonlyArray<Event>): ReadonlyArray<string> => events.map((e) => e.id);\n\nfunction sameRef(a: Event | undefined, b: Event | undefined): boolean {\n return a === b;\n}\n\nfunction sameList(a: ReadonlyArray<Event>, b: ReadonlyArray<Event>): boolean {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Synchronous reactive in-memory event store backed by {@link MemoryIndex}. The single read source\n * for Nostr events on the UI side: `Watch` snapshots keep referential stability for\n * `useSyncExternalStore` and are invalidated on matching writes, deletions and tombstones. Writes\n * batch notifications into one microtask flush.\n */\nexport class ReactiveEventStore {\n readonly #index: MemoryIndex;\n readonly #maxEvents: number;\n readonly #maxSeenOnEntries: number;\n\n #version = 0;\n\n // id -> recency-ordered access order (delete+add moves to the newest end)\n readonly #recency = new Set<string>();\n // id -> relay urls (≤ SEEN_ON_PER_ID); Map insertion order for eviction\n readonly #seenOn = new Map<string, string[]>();\n\n readonly #idWatches = new Map<string, Set<WatchHandle>>();\n readonly #addressWatches = new Map<string, Set<WatchHandle>>();\n readonly #eventCache = new Map<string, WatchImpl<Event | undefined>>();\n readonly #addressCache = new Map<string, WatchImpl<Event | undefined>>();\n readonly #queryCache = new Map<string, WatchImpl<ReadonlyArray<Event>>>();\n readonly #queryRegistry = new Set<WatchHandle>();\n\n // Event-id → pin refcount across registered watch snapshots; eviction skips pinned ids.\n readonly #pinned = new Map<string, number>();\n // Registered watch → ids it currently pins (repin/unpin need the old set).\n readonly #watchPins = new Map<WatchHandle, ReadonlyArray<string>>();\n\n readonly #dirty = new Set<WatchHandle>();\n #flushScheduled = false;\n readonly #insertListeners = new Set<(event: Event) => void>();\n readonly #removeListeners = new Set<(event: Event) => void>();\n\n readonly #watchHost: WatchHost = {\n version: () => this.#version,\n register: (watch) => {\n this.#registerWatch(watch);\n if (!this.#watchPins.has(watch)) {\n const ids = watch.pinnedIds;\n this.#watchPins.set(watch, ids);\n this.#pin(ids);\n }\n },\n unregister: (watch) => {\n if (watch.subscribed) {\n return;\n }\n this.#unregisterWatch(watch);\n const ids = this.#watchPins.get(watch);\n if (ids !== undefined) {\n this.#watchPins.delete(watch);\n this.#unpin(ids);\n }\n },\n repin: (watch) => {\n const old = this.#watchPins.get(watch);\n if (old === undefined) {\n return;\n }\n const next = watch.pinnedIds;\n this.#unpin(old);\n this.#pin(next);\n this.#watchPins.set(watch, next);\n },\n };\n\n constructor(opts?: ReactiveEventStoreOptions) {\n this.#maxEvents = opts?.maxEvents ?? 50_000;\n this.#maxSeenOnEntries = opts?.maxSeenOnEntries ?? 20_000;\n this.#index = new MemoryIndex({\n maxTombstones: opts?.maxTombstones ?? 100_000,\n // Keep evicted replaceable winners rejectable while their body is gone.\n maxWatermarks: this.#maxEvents,\n onInsert: (event) => this.#onInsert(event),\n onRemove: (event) => this.#onRemove(event),\n });\n }\n\n add(event: Event, relayUrl?: string): PutResult {\n const result = this.#index.put(event);\n // Stored events are canonical; seenOn keys are their lowercase ids.\n const { id } = event;\n if (\n relayUrl !== undefined &&\n result !== \"ephemeral\" &&\n result !== \"rejected\" &&\n result !== \"invalid\"\n ) {\n this.#recordSeen(id, relayUrl);\n }\n // The just-inserted event must survive its own eviction pass even when\n // every older entry is pinned by a subscribed watch snapshot.\n this.#evictIfNeeded(id);\n return result;\n }\n\n addMany(events: ReadonlyArray<Event>, relayUrl?: string): PutResult[] {\n const results: PutResult[] = [];\n for (const event of events) {\n results.push(this.add(event, relayUrl));\n }\n return results;\n }\n\n /** Record a relay sighting for an id already in the index. No notification. */\n markSeen(id: string, relayUrl: string): boolean {\n const key = id.toLowerCase();\n if (this.#index.get(key) === undefined) {\n return false;\n }\n this.#recordSeen(key, relayUrl);\n return true;\n }\n\n /** Bulk load (initial hydration): no seenOn, one batched notification. */\n hydrate(events: ReadonlyArray<Event>): void {\n // Insert oldest-first so recency ends with the newest entries hottest.\n const sorted = [...events].sort(itemCompare);\n for (const event of sorted) {\n this.#index.put(event);\n }\n this.#evictIfNeeded();\n }\n\n remove(ids: ReadonlyArray<string>): number {\n return this.#index.remove(ids);\n }\n\n clear(): void {\n this.#index.clear();\n this.#seenOn.clear();\n this.#recency.clear();\n this.#version += 1;\n this.#invalidateAll();\n }\n\n get(id: string): Event | undefined {\n const event = this.#index.get(id);\n if (event) {\n this.#touch(event.id);\n }\n return event;\n }\n\n getReplaceable(kind: number, pubkey: string, d?: string): Event | undefined {\n return this.getByAddress(formatEventAddress(kind, pubkey, d ?? \"\"));\n }\n\n getByAddress(address: string): Event | undefined {\n const event = this.#index.getByAddress(address);\n if (event) {\n this.#touch(event.id);\n }\n return event;\n }\n\n query(filters: ReadonlyArray<Filter>): ReadonlyArray<Event> {\n const events = this.#index.query(filters);\n // Results come back newest-first; touch oldest→newest so the newest\n // entries end up hottest in the recency order.\n for (const event of [...events].reverse()) {\n this.#touch(event.id);\n }\n return events;\n }\n\n isDeleted(idOrAddress: string): boolean {\n return this.#index.isDeleted(idOrAddress);\n }\n\n seenOn(id: string): ReadonlyArray<string> {\n return this.#seenOn.get(id.toLowerCase()) ?? [];\n }\n\n get size(): number {\n return this.#index.size;\n }\n\n watchEvent(id: string): Watch<Event | undefined> {\n const key = id.toLowerCase();\n let watch = this.#eventCache.get(key);\n if (!watch) {\n watch = new WatchImpl(this.#watchHost, \"event\", key, () => this.get(key), sameRef, EVENT_IDS);\n this.#eventCache.set(key, watch);\n }\n return watch;\n }\n\n watchReplaceable(kind: number, pubkey: string, d?: string): Watch<Event | undefined> {\n const address = formatEventAddress(kind, pubkey, d ?? \"\");\n let watch = this.#addressCache.get(address);\n if (!watch) {\n watch = new WatchImpl(\n this.#watchHost,\n \"replaceable\",\n address,\n () => this.getByAddress(address),\n sameRef,\n EVENT_IDS,\n );\n this.#addressCache.set(address, watch);\n }\n return watch;\n }\n\n watchQuery(filters: ReadonlyArray<Filter>): Watch<ReadonlyArray<Event>> {\n const key = filterFingerprint(filters);\n let watch = this.#queryCache.get(key);\n if (!watch) {\n const list = [...filters];\n watch = new WatchImpl(\n this.#watchHost,\n \"query\",\n key,\n () => this.query(list),\n sameList,\n LIST_IDS,\n list,\n );\n this.#queryCache.set(key, watch);\n }\n return watch;\n }\n\n /** Synchronous listener for every physical index insert (after update). */\n onInsert(listener: (event: Event) => void): () => void {\n this.#insertListeners.add(listener);\n return () => {\n this.#insertListeners.delete(listener);\n };\n }\n\n /** Synchronous listener for every physical index remove (after watch invalidation). */\n onRemove(listener: (event: Event) => void): () => void {\n this.#removeListeners.add(listener);\n return () => {\n this.#removeListeners.delete(listener);\n };\n }\n\n #registerWatch(watch: WatchHandle): void {\n switch (watch.kind) {\n case \"event\":\n addToSetMap(this.#idWatches, watch.key, watch);\n return;\n case \"replaceable\":\n addToSetMap(this.#addressWatches, watch.key, watch);\n return;\n case \"query\":\n this.#queryRegistry.add(watch);\n }\n }\n\n #unregisterWatch(watch: WatchHandle): void {\n switch (watch.kind) {\n case \"event\":\n removeFromSetMap(this.#idWatches, watch.key, watch);\n if (this.#eventCache.get(watch.key) === watch) {\n this.#eventCache.delete(watch.key);\n }\n return;\n case \"replaceable\":\n removeFromSetMap(this.#addressWatches, watch.key, watch);\n if (this.#addressCache.get(watch.key) === watch) {\n this.#addressCache.delete(watch.key);\n }\n return;\n case \"query\":\n this.#queryRegistry.delete(watch);\n if (this.#queryCache.get(watch.key) === watch) {\n this.#queryCache.delete(watch.key);\n }\n }\n }\n\n #touch(id: string): void {\n this.#recency.delete(id);\n this.#recency.add(id);\n }\n\n #recordSeen(id: string, relayUrl: string): void {\n const url = normalizeURL(relayUrl);\n let urls = this.#seenOn.get(id);\n if (!urls) {\n urls = [];\n this.#seenOn.set(id, urls);\n }\n if (!urls.includes(url) && urls.length < SEEN_ON_PER_ID) {\n urls.push(url);\n }\n trimOldest(this.#seenOn, this.#maxSeenOnEntries);\n }\n\n #onInsert(event: Event): void {\n this.#version += 1;\n this.#touch(event.id);\n this.#invalidateByEvent(event);\n for (const listener of this.#insertListeners) {\n invokeSafely(() => listener(event));\n }\n }\n\n #onRemove(event: Event): void {\n this.#version += 1;\n this.#recency.delete(event.id);\n this.#invalidateByEvent(event);\n for (const listener of this.#removeListeners) {\n invokeSafely(() => listener(event));\n }\n }\n\n #invalidateByEvent(event: Event): void {\n const byId = this.#idWatches.get(event.id);\n if (byId) {\n for (const watch of byId) {\n this.#markDirty(watch);\n }\n }\n const address = eventAddress(event);\n if (address !== undefined) {\n const byAddress = this.#addressWatches.get(address);\n if (byAddress) {\n for (const watch of byAddress) {\n this.#markDirty(watch);\n }\n }\n }\n // A kind-5 deletion marks its targets tombstoned even when they are not in\n // the index, so nothing else invalidates a watch keyed on the target.\n if (event.kind === Kind.EventDeletion) {\n for (const tag of event.tags) {\n if (tag[0] === \"e\" && tag[1] !== undefined) {\n const watches = this.#idWatches.get(tag[1].toLowerCase());\n if (watches) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n } else if (tag[0] === \"a\" && tag[1] !== undefined) {\n const coord = parseEventAddress(tag[1]);\n if (coord === undefined) {\n continue;\n }\n const watches = this.#addressWatches.get(\n formatEventAddress(coord.kind, coord.pubkey, coord.identifier),\n );\n if (watches) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n }\n }\n }\n for (const watch of this.#queryRegistry) {\n if (watch.filters !== undefined && matchFilters(watch.filters, event)) {\n this.#markDirty(watch);\n }\n }\n }\n\n #invalidateAll(): void {\n for (const watches of this.#idWatches.values()) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n for (const watches of this.#addressWatches.values()) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n for (const watch of this.#queryRegistry) {\n this.#markDirty(watch);\n }\n this.#scheduleFlush();\n }\n\n #markDirty(watch: WatchHandle): void {\n watch.markDirty();\n this.#dirty.add(watch);\n this.#scheduleFlush();\n }\n\n #scheduleFlush(): void {\n if (this.#flushScheduled) {\n return;\n }\n this.#flushScheduled = true;\n queueMicrotask(() => {\n // Keep the flag set while flushing: writes re-entered from a\n // subscriber land in #dirty and are notified in the same pass.\n while (this.#dirty.size > 0) {\n const batch = [...this.#dirty];\n this.#dirty.clear();\n for (const watch of batch) {\n watch.notify();\n }\n }\n this.#flushScheduled = false;\n });\n }\n\n #pin(ids: ReadonlyArray<string>): void {\n for (const id of ids) {\n this.#pinned.set(id, (this.#pinned.get(id) ?? 0) + 1);\n }\n }\n\n #unpin(ids: ReadonlyArray<string>): void {\n for (const id of ids) {\n const count = (this.#pinned.get(id) ?? 0) - 1;\n if (count <= 0) {\n this.#pinned.delete(id);\n } else {\n this.#pinned.set(id, count);\n }\n }\n }\n\n #evictIfNeeded(protectedId?: string): void {\n if (this.#index.size <= this.#maxEvents) {\n return;\n }\n const evicting: string[] = [];\n for (const id of this.#recency) {\n if (this.#index.size - evicting.length <= this.#maxEvents) {\n break;\n }\n if (this.#pinned.has(id) || id === protectedId) {\n continue;\n }\n const event = this.#index.get(id);\n if (event === undefined) {\n continue;\n }\n // Replaceable/addressable winners evict like any other event; the index\n // records a watermark so stale versions stay rejected afterwards.\n evicting.push(id);\n }\n this.#index.evict(evicting);\n for (const id of evicting) {\n this.#recency.delete(id);\n }\n }\n}\n"],"mappings":"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"}
|