@qntx/nostr 0.0.1

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.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/builder-BphJa-Y-.d.mts +67 -0
  4. package/dist/builder-q0VsvnYE.mjs +158 -0
  5. package/dist/builder-q0VsvnYE.mjs.map +1 -0
  6. package/dist/client-Bd8B1mYm.mjs +835 -0
  7. package/dist/client-Bd8B1mYm.mjs.map +1 -0
  8. package/dist/client.d.mts +2 -0
  9. package/dist/client.mjs +2 -0
  10. package/dist/core.d.mts +8 -0
  11. package/dist/core.mjs +9 -0
  12. package/dist/error-B49rbDzT.d.mts +7 -0
  13. package/dist/error-CwBxle6N.mjs +13 -0
  14. package/dist/error-CwBxle6N.mjs.map +1 -0
  15. package/dist/error-DZEs5OuZ.mjs +24 -0
  16. package/dist/error-DZEs5OuZ.mjs.map +1 -0
  17. package/dist/error-WEFS-R-4.d.mts +20 -0
  18. package/dist/event-B5D2jxt_.d.mts +108 -0
  19. package/dist/event-CzoQEFMF.mjs +188 -0
  20. package/dist/event-CzoQEFMF.mjs.map +1 -0
  21. package/dist/filter-BxwjEkZp.mjs +113 -0
  22. package/dist/filter-BxwjEkZp.mjs.map +1 -0
  23. package/dist/filter-DUEKP3q1.d.mts +40 -0
  24. package/dist/gossip-3xkRRCm_.mjs +220 -0
  25. package/dist/gossip-3xkRRCm_.mjs.map +1 -0
  26. package/dist/gossip-C5RVB1UB.d.mts +71 -0
  27. package/dist/gossip.d.mts +3 -0
  28. package/dist/gossip.mjs +2 -0
  29. package/dist/http-6vevbBlu.d.mts +22 -0
  30. package/dist/http-D2rhokZo.mjs +24 -0
  31. package/dist/http-D2rhokZo.mjs.map +1 -0
  32. package/dist/index-BYMnhRIC.d.mts +161 -0
  33. package/dist/index-C0C4e-Gn.d.mts +55 -0
  34. package/dist/index-CaxrMD1A.d.mts +1 -0
  35. package/dist/index-DaRyjo7O.d.mts +77 -0
  36. package/dist/index-DgOugrpF.d.mts +275 -0
  37. package/dist/index-EwL6fHLo.d.mts +267 -0
  38. package/dist/index-k2YhCgsh.d.mts +57 -0
  39. package/dist/index.d.mts +22 -0
  40. package/dist/index.mjs +23 -0
  41. package/dist/key-BiGxGLhg.d.mts +37 -0
  42. package/dist/key-Dn-aX4x4.mjs +130 -0
  43. package/dist/key-Dn-aX4x4.mjs.map +1 -0
  44. package/dist/kind-BLl4cLOc.mjs +59 -0
  45. package/dist/kind-BLl4cLOc.mjs.map +1 -0
  46. package/dist/loaders-BNQszDAn.mjs +767 -0
  47. package/dist/loaders-BNQszDAn.mjs.map +1 -0
  48. package/dist/loaders.d.mts +3 -0
  49. package/dist/loaders.mjs +2 -0
  50. package/dist/memory-BCmhZPek.mjs +461 -0
  51. package/dist/memory-BCmhZPek.mjs.map +1 -0
  52. package/dist/message-C_Uj-Y-c.mjs +179 -0
  53. package/dist/message-C_Uj-Y-c.mjs.map +1 -0
  54. package/dist/message-DJC-Jz6T.d.mts +32 -0
  55. package/dist/nip17-BWq-Kfq5.d.mts +38 -0
  56. package/dist/nip19-D7905oIv.d.mts +63 -0
  57. package/dist/nip46-C_DoxiS_.d.mts +51 -0
  58. package/dist/nip59-DljW_sYh.d.mts +76 -0
  59. package/dist/nip65-Di5hZRuV.d.mts +21 -0
  60. package/dist/nip77-ByxlgoQO.d.mts +55 -0
  61. package/dist/nips/blossom.d.mts +93 -0
  62. package/dist/nips/blossom.mjs +325 -0
  63. package/dist/nips/blossom.mjs.map +1 -0
  64. package/dist/nips/nip04.d.mts +8 -0
  65. package/dist/nips/nip04.mjs +41 -0
  66. package/dist/nips/nip04.mjs.map +1 -0
  67. package/dist/nips/nip05.d.mts +89 -0
  68. package/dist/nips/nip05.mjs +147 -0
  69. package/dist/nips/nip05.mjs.map +1 -0
  70. package/dist/nips/nip10.d.mts +43 -0
  71. package/dist/nips/nip10.mjs +194 -0
  72. package/dist/nips/nip10.mjs.map +1 -0
  73. package/dist/nips/nip11.d.mts +43 -0
  74. package/dist/nips/nip11.mjs +99 -0
  75. package/dist/nips/nip11.mjs.map +1 -0
  76. package/dist/nips/nip13.d.mts +23 -0
  77. package/dist/nips/nip13.mjs +88 -0
  78. package/dist/nips/nip13.mjs.map +1 -0
  79. package/dist/nips/nip17.d.mts +2 -0
  80. package/dist/nips/nip17.mjs +138 -0
  81. package/dist/nips/nip17.mjs.map +1 -0
  82. package/dist/nips/nip19.d.mts +2 -0
  83. package/dist/nips/nip19.mjs +174 -0
  84. package/dist/nips/nip19.mjs.map +1 -0
  85. package/dist/nips/nip21.d.mts +17 -0
  86. package/dist/nips/nip21.mjs +29 -0
  87. package/dist/nips/nip21.mjs.map +1 -0
  88. package/dist/nips/nip27.d.mts +42 -0
  89. package/dist/nips/nip27.mjs +175 -0
  90. package/dist/nips/nip27.mjs.map +1 -0
  91. package/dist/nips/nip42.d.mts +12 -0
  92. package/dist/nips/nip42.mjs +22 -0
  93. package/dist/nips/nip42.mjs.map +1 -0
  94. package/dist/nips/nip44.d.mts +12 -0
  95. package/dist/nips/nip44.mjs +125 -0
  96. package/dist/nips/nip44.mjs.map +1 -0
  97. package/dist/nips/nip46.d.mts +2 -0
  98. package/dist/nips/nip46.mjs +132 -0
  99. package/dist/nips/nip46.mjs.map +1 -0
  100. package/dist/nips/nip49.d.mts +12 -0
  101. package/dist/nips/nip49.mjs +80 -0
  102. package/dist/nips/nip49.mjs.map +1 -0
  103. package/dist/nips/nip51.d.mts +88 -0
  104. package/dist/nips/nip51.mjs +246 -0
  105. package/dist/nips/nip51.mjs.map +1 -0
  106. package/dist/nips/nip57.d.mts +43 -0
  107. package/dist/nips/nip57.mjs +230 -0
  108. package/dist/nips/nip57.mjs.map +1 -0
  109. package/dist/nips/nip59.d.mts +2 -0
  110. package/dist/nips/nip59.mjs +171 -0
  111. package/dist/nips/nip59.mjs.map +1 -0
  112. package/dist/nips/nip65.d.mts +2 -0
  113. package/dist/nips/nip65.mjs +76 -0
  114. package/dist/nips/nip65.mjs.map +1 -0
  115. package/dist/nips/nip77.d.mts +2 -0
  116. package/dist/nips/nip77.mjs +421 -0
  117. package/dist/nips/nip77.mjs.map +1 -0
  118. package/dist/nips/nip96.d.mts +31 -0
  119. package/dist/nips/nip96.mjs +95 -0
  120. package/dist/nips/nip96.mjs.map +1 -0
  121. package/dist/nips/nip98.d.mts +28 -0
  122. package/dist/nips/nip98.mjs +91 -0
  123. package/dist/nips/nip98.mjs.map +1 -0
  124. package/dist/nostr_crypto_wasm_bg-hi5aXi5P.mjs +4 -0
  125. package/dist/nostr_crypto_wasm_bg.wasm +0 -0
  126. package/dist/pool-C_j1OmBB.mjs +1881 -0
  127. package/dist/pool-C_j1OmBB.mjs.map +1 -0
  128. package/dist/pool-DhRi7l-b.d.mts +297 -0
  129. package/dist/relay.d.mts +4 -0
  130. package/dist/relay.mjs +2 -0
  131. package/dist/signer-CZ6K6FUc.mjs +473 -0
  132. package/dist/signer-CZ6K6FUc.mjs.map +1 -0
  133. package/dist/signer.d.mts +3 -0
  134. package/dist/signer.mjs +2 -0
  135. package/dist/storage-DOy0iLRE.mjs +870 -0
  136. package/dist/storage-DOy0iLRE.mjs.map +1 -0
  137. package/dist/storage.d.mts +4 -0
  138. package/dist/storage.mjs +4 -0
  139. package/dist/types-BzyADqnw.d.mts +19 -0
  140. package/dist/types-Dn24TPFs.d.mts +43 -0
  141. package/dist/util-6tfLQ9JF.mjs +73 -0
  142. package/dist/util-6tfLQ9JF.mjs.map +1 -0
  143. package/dist/wasm.d.mts +19 -0
  144. package/dist/wasm.mjs +237 -0
  145. package/dist/wasm.mjs.map +1 -0
  146. package/package.json +204 -0
@@ -0,0 +1,870 @@
1
+ import { t as Kind } from "./kind-BLl4cLOc.mjs";
2
+ import { a as itemCompare, h as eventAddress, i as isReplaceableWinner, u as sortEvents, y as parseEventAddress } from "./event-CzoQEFMF.mjs";
3
+ import { o as matchFilter } from "./filter-BxwjEkZp.mjs";
4
+ import { n as toStorageError, t as StorageError } from "./error-CwBxle6N.mjs";
5
+ import { a as DeletionState, i as outboxBoundKey, n as decidePut, o as planDeletion, r as normalizeEvent } from "./memory-BCmhZPek.mjs";
6
+ //#region src/storage/idb-types.ts
7
+ const EVENTS = "events";
8
+ const TAG_REFS = "tag_refs";
9
+ const ADDRESSES = "addresses";
10
+ const TOMBSTONES = "tombstones";
11
+ const OUTBOX_BOUNDS = "outbox_bounds";
12
+ const WRITE_STORES = [
13
+ EVENTS,
14
+ TAG_REFS,
15
+ ADDRESSES,
16
+ TOMBSTONES
17
+ ];
18
+ //#endregion
19
+ //#region src/storage/idb-helpers.ts
20
+ function reqOf(req) {
21
+ return new Promise((resolve, reject) => {
22
+ req.onsuccess = () => resolve(req.result);
23
+ req.onerror = () => reject(req.error ?? new StorageError("IndexedDB request failed"));
24
+ });
25
+ }
26
+ function txDone(tx) {
27
+ return new Promise((resolve, reject) => {
28
+ tx.oncomplete = () => resolve();
29
+ tx.onerror = () => reject(tx.error ?? new StorageError("IndexedDB transaction failed"));
30
+ });
31
+ }
32
+ function walkCursor(source, range, direction, visit) {
33
+ return new Promise((resolve, reject) => {
34
+ const req = source.openCursor(range, direction);
35
+ req.onerror = () => reject(req.error ?? new StorageError("IndexedDB cursor failed"));
36
+ req.onsuccess = () => {
37
+ const cursor = req.result;
38
+ if (!cursor) {
39
+ resolve();
40
+ return;
41
+ }
42
+ if (visit(cursor)) {
43
+ resolve();
44
+ return;
45
+ }
46
+ cursor.continue();
47
+ };
48
+ });
49
+ }
50
+ function tagRefKey(name, value, id) {
51
+ return `${name}:${value.toLowerCase()}:${id.toLowerCase()}`;
52
+ }
53
+ function writeTagRefs(store, event) {
54
+ for (const tag of event.tags) {
55
+ if (tag[0] !== "e" && tag[0] !== "p" || tag[1] === void 0) continue;
56
+ const name = tag[0];
57
+ const value = tag[1].toLowerCase();
58
+ const id = event.id.toLowerCase();
59
+ store.put({
60
+ key: tagRefKey(name, value, id),
61
+ name,
62
+ value,
63
+ id,
64
+ created_at: event.created_at
65
+ });
66
+ }
67
+ }
68
+ function deleteStoredEvent(tx, event, addressRow) {
69
+ const tagRefs = tx.objectStore(TAG_REFS);
70
+ for (const tag of event.tags) if ((tag[0] === "e" || tag[0] === "p") && tag[1] !== void 0) tagRefs.delete(tagRefKey(tag[0], tag[1], event.id));
71
+ if (addressRow?.id === event.id) tx.objectStore(ADDRESSES).delete(addressRow.address);
72
+ tx.objectStore(EVENTS).delete(event.id);
73
+ }
74
+ function persistPlanTombstones(store, plan, coordIds, deletion) {
75
+ for (const id of plan.removeIds) {
76
+ store.put({
77
+ key: `id:${id}`,
78
+ type: "id"
79
+ });
80
+ store.delete(`pending:${id}`);
81
+ }
82
+ for (const id of coordIds) {
83
+ store.put({
84
+ key: `id:${id}`,
85
+ type: "id"
86
+ });
87
+ store.delete(`pending:${id}`);
88
+ }
89
+ for (const p of plan.pendingIds) {
90
+ if (deletion.ids.has(p.id)) continue;
91
+ store.put({
92
+ key: `pending:${p.id}`,
93
+ type: "pending",
94
+ pubkey: p.pubkey.toLowerCase()
95
+ });
96
+ }
97
+ for (const c of plan.coordinates) {
98
+ const prev = deletion.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;
99
+ store.put({
100
+ key: `coord:${c.key}`,
101
+ type: "coord",
102
+ until: Math.max(prev, c.until)
103
+ });
104
+ }
105
+ }
106
+ function tombstonesToPlan(rows) {
107
+ const plan = {
108
+ removeIds: [],
109
+ pendingIds: [],
110
+ coordinates: []
111
+ };
112
+ for (const row of rows) {
113
+ if (!row || typeof row !== "object") continue;
114
+ const r = row;
115
+ if (r.type === "id" && typeof r.key === "string" && r.key.startsWith("id:")) {
116
+ plan.removeIds.push(r.key.slice(3).toLowerCase());
117
+ continue;
118
+ }
119
+ if (r.type === "pending" && typeof r.key === "string" && r.key.startsWith("pending:") && typeof r.pubkey === "string") {
120
+ plan.pendingIds.push({
121
+ id: r.key.slice(8).toLowerCase(),
122
+ pubkey: r.pubkey.toLowerCase()
123
+ });
124
+ continue;
125
+ }
126
+ if (r.type === "coord" && typeof r.key === "string" && r.key.startsWith("coord:") && typeof r.until === "number") plan.coordinates.push({
127
+ key: r.key.slice(6),
128
+ until: r.until
129
+ });
130
+ }
131
+ return plan;
132
+ }
133
+ function applyPutIndexedDb(tx, s, d) {
134
+ const events = tx.objectStore(EVENTS);
135
+ const tagRefs = tx.objectStore(TAG_REFS);
136
+ const addresses = tx.objectStore(ADDRESSES);
137
+ const tombstones = tx.objectStore(TOMBSTONES);
138
+ switch (d.action) {
139
+ case "skip": return d.result;
140
+ case "tombstone":
141
+ tombstones.put({
142
+ key: `id:${d.event.id}`,
143
+ type: "id"
144
+ });
145
+ tombstones.delete(`pending:${d.event.id}`);
146
+ s.deletion.ids.add(d.event.id);
147
+ s.deletion.pending.delete(d.event.id);
148
+ return "duplicate";
149
+ case "delete":
150
+ s.deletion.pending.delete(d.event.id);
151
+ persistPlanTombstones(tombstones, d.plan, d.coordIds, s.deletion);
152
+ s.deletion.absorb(d.plan);
153
+ for (const id of d.coordIds) s.deletion.ids.add(id);
154
+ events.put(d.event);
155
+ writeTagRefs(tagRefs, d.event);
156
+ return "deleted";
157
+ case "insert":
158
+ events.put(d.event);
159
+ writeTagRefs(tagRefs, d.event);
160
+ if (d.address) {
161
+ addresses.put({
162
+ address: d.address,
163
+ id: d.event.id,
164
+ created_at: d.event.created_at
165
+ });
166
+ s.replaceable.set(d.address, d.event.id);
167
+ }
168
+ return d.result;
169
+ }
170
+ }
171
+ //#endregion
172
+ //#region src/storage/idb-query.ts
173
+ function compareEventsDesc(a, b) {
174
+ if (a.created_at !== b.created_at) return b.created_at - a.created_at;
175
+ return a.id.localeCompare(b.id);
176
+ }
177
+ function prefixRange(prefix, since, until) {
178
+ return idbKeyRange().bound([...prefix, since ?? 0], [...prefix, until ?? Number.MAX_SAFE_INTEGER]);
179
+ }
180
+ function createdAtRange(since, until) {
181
+ return idbKeyRange().bound(since ?? 0, until ?? Number.MAX_SAFE_INTEGER);
182
+ }
183
+ function idbKeyRange() {
184
+ return globalThis.IDBKeyRange;
185
+ }
186
+ function epTagPrefixes(filter) {
187
+ const out = [];
188
+ for (const name of ["e", "p"]) {
189
+ const values = filter[`#${name}`];
190
+ if (!values) continue;
191
+ for (const value of values) out.push({
192
+ name,
193
+ value: value.toLowerCase()
194
+ });
195
+ }
196
+ return out;
197
+ }
198
+ function eventCursor(source, range) {
199
+ return {
200
+ open: () => source.openCursor(range, "prev"),
201
+ read: (cursor, ok) => {
202
+ ok(cursor.value);
203
+ }
204
+ };
205
+ }
206
+ function tagCursor(index, events, range) {
207
+ return {
208
+ open: () => index.openCursor(range, "prev"),
209
+ read: (cursor, ok, err) => {
210
+ const row = cursor.value;
211
+ const req = events.get(row.id);
212
+ req.onerror = () => err(req.error ?? new StorageError("IndexedDB get failed"));
213
+ req.onsuccess = () => ok(req.result);
214
+ }
215
+ };
216
+ }
217
+ function scanIds(tx, filter, accept, take) {
218
+ const ids = filter.ids ?? [];
219
+ const events = tx.objectStore(EVENTS);
220
+ const reqs = ids.map((id) => events.get(id.toLowerCase()));
221
+ return Promise.all(reqs.map((req) => reqOf(req))).then((rows) => {
222
+ const matched = [];
223
+ const seen = /* @__PURE__ */ new Set();
224
+ for (const event of rows) {
225
+ if (!event || seen.has(event.id) || !accept(event)) continue;
226
+ seen.add(event.id);
227
+ matched.push(event);
228
+ }
229
+ sortEvents(matched);
230
+ const out = filter.limit !== void 0 ? matched.slice(0, filter.limit) : matched;
231
+ for (const event of out) if (take(event)) break;
232
+ });
233
+ }
234
+ /** IDB auto-commits when onsuccess returns with no outstanding requests. */
235
+ function kWayMerge(openers, accept, take) {
236
+ return new Promise((resolve, reject) => {
237
+ if (openers.length === 0) {
238
+ resolve();
239
+ return;
240
+ }
241
+ const slots = openers.map(() => ({
242
+ cursor: void 0,
243
+ head: void 0
244
+ }));
245
+ const seen = /* @__PURE__ */ new Set();
246
+ let inflight = 0;
247
+ let phase = "merge";
248
+ let drainT = 0;
249
+ let drainBuf = [];
250
+ const finish = () => {
251
+ if (phase === "done") return;
252
+ phase = "done";
253
+ resolve();
254
+ };
255
+ const fail = (error) => {
256
+ if (phase === "done") return;
257
+ phase = "done";
258
+ reject(error);
259
+ };
260
+ const stepCursor = (i) => {
261
+ const cursor = slots[i].cursor;
262
+ if (!cursor) return;
263
+ inflight++;
264
+ cursor.continue();
265
+ };
266
+ const emitDrain = () => {
267
+ sortEvents(drainBuf);
268
+ for (const event of drainBuf) {
269
+ if (seen.has(event.id) || !accept(event)) continue;
270
+ seen.add(event.id);
271
+ if (take(event)) {
272
+ finish();
273
+ return;
274
+ }
275
+ }
276
+ drainBuf = [];
277
+ phase = "merge";
278
+ pump();
279
+ };
280
+ const pump = () => {
281
+ if (phase === "done" || inflight > 0) return;
282
+ if (phase === "drain") {
283
+ emitDrain();
284
+ return;
285
+ }
286
+ let best;
287
+ for (let i = 0; i < slots.length; i++) {
288
+ const event = slots[i].head;
289
+ if (!event) continue;
290
+ if (!best || compareEventsDesc(event, best) < 0) best = event;
291
+ }
292
+ if (!best) {
293
+ finish();
294
+ return;
295
+ }
296
+ phase = "drain";
297
+ drainT = best.created_at;
298
+ drainBuf = [];
299
+ for (let i = 0; i < slots.length; i++) {
300
+ const event = slots[i].head;
301
+ if (!event || event.created_at !== drainT) continue;
302
+ drainBuf.push(event);
303
+ slots[i].head = void 0;
304
+ stepCursor(i);
305
+ }
306
+ if (inflight === 0) pump();
307
+ };
308
+ const onEvent = (i, event) => {
309
+ if (phase === "done") return;
310
+ const slot = slots[i];
311
+ const cursor = slot.cursor;
312
+ if (!event) {
313
+ if (cursor) {
314
+ inflight++;
315
+ cursor.continue();
316
+ } else pump();
317
+ return;
318
+ }
319
+ if (phase === "drain") {
320
+ if (event.created_at === drainT) {
321
+ drainBuf.push(event);
322
+ if (cursor) {
323
+ inflight++;
324
+ cursor.continue();
325
+ } else pump();
326
+ return;
327
+ }
328
+ slot.head = event;
329
+ pump();
330
+ return;
331
+ }
332
+ slot.head = event;
333
+ pump();
334
+ };
335
+ for (let i = 0; i < openers.length; i++) {
336
+ const req = openers[i].open();
337
+ req.onerror = () => fail(req.error ?? new StorageError("IndexedDB cursor failed"));
338
+ inflight++;
339
+ req.onsuccess = () => {
340
+ inflight--;
341
+ if (phase === "done") return;
342
+ const cursor = req.result;
343
+ const slot = slots[i];
344
+ if (!cursor) {
345
+ slot.cursor = void 0;
346
+ slot.head = void 0;
347
+ pump();
348
+ return;
349
+ }
350
+ slot.cursor = cursor;
351
+ inflight++;
352
+ openers[i].read(cursor, (event) => {
353
+ inflight--;
354
+ onEvent(i, event);
355
+ }, (error) => {
356
+ inflight--;
357
+ fail(error);
358
+ });
359
+ };
360
+ }
361
+ });
362
+ }
363
+ function scanFilter(tx, filter, accept, take) {
364
+ if (filter.limit === 0) return Promise.resolve();
365
+ if (filter.since !== void 0 && filter.until !== void 0 && filter.since > filter.until) return Promise.resolve();
366
+ if (filter.ids) return scanIds(tx, filter, accept, take);
367
+ const events = tx.objectStore(EVENTS);
368
+ const openers = [];
369
+ if (filter.authors && filter.kinds) {
370
+ const index = events.index("kind_pubkey_created_at");
371
+ for (const kind of filter.kinds) for (const pk of filter.authors) openers.push(eventCursor(index, prefixRange([kind, pk.toLowerCase()], filter.since, filter.until)));
372
+ } else if (filter.authors) {
373
+ const index = events.index("pubkey_created_at");
374
+ for (const pk of filter.authors) openers.push(eventCursor(index, prefixRange([pk.toLowerCase()], filter.since, filter.until)));
375
+ } else if (filter.kinds) {
376
+ const index = events.index("kind_created_at");
377
+ for (const kind of filter.kinds) openers.push(eventCursor(index, prefixRange([kind], filter.since, filter.until)));
378
+ } else {
379
+ const tags = epTagPrefixes(filter);
380
+ if (tags.length > 0) {
381
+ const index = tx.objectStore(TAG_REFS).index("name_value_created");
382
+ for (const tag of tags) openers.push(tagCursor(index, events, prefixRange([tag.name, tag.value], filter.since, filter.until)));
383
+ } else openers.push(eventCursor(events.index("created_at"), createdAtRange(filter.since, filter.until)));
384
+ }
385
+ return kWayMerge(openers, accept, take);
386
+ }
387
+ function openDb(dbName) {
388
+ return new Promise((resolve, reject) => {
389
+ const req = globalThis.indexedDB.open(dbName, 4);
390
+ req.onupgradeneeded = (ev) => {
391
+ const db = req.result;
392
+ const oldVersion = ev.oldVersion;
393
+ const tx = ev.target.transaction;
394
+ if (oldVersion < 1) db.createObjectStore(EVENTS, { keyPath: "id" });
395
+ if (oldVersion < 2) {
396
+ const events = tx.objectStore(EVENTS);
397
+ events.createIndex("created_at", "created_at");
398
+ events.createIndex("kind_created_at", ["kind", "created_at"]);
399
+ events.createIndex("pubkey_created_at", ["pubkey", "created_at"]);
400
+ events.createIndex("kind_pubkey_created_at", [
401
+ "kind",
402
+ "pubkey",
403
+ "created_at"
404
+ ]);
405
+ db.createObjectStore(TAG_REFS, { keyPath: "key" }).createIndex("name_value_created", [
406
+ "name",
407
+ "value",
408
+ "created_at"
409
+ ]);
410
+ db.createObjectStore(ADDRESSES, { keyPath: "address" });
411
+ db.createObjectStore(TOMBSTONES, { keyPath: "key" });
412
+ if (oldVersion >= 1) {
413
+ const all = events.getAll();
414
+ all.onsuccess = () => {
415
+ migrateV1Events(tx, all.result ?? []);
416
+ compactSupersededReplaceables(tx);
417
+ };
418
+ }
419
+ } else if (oldVersion < 3) compactSupersededReplaceables(tx);
420
+ if (oldVersion < 4) db.createObjectStore(OUTBOX_BOUNDS, { keyPath: "key" });
421
+ };
422
+ req.onsuccess = () => resolve(req.result);
423
+ req.onerror = () => reject(req.error ?? new StorageError("IndexedDB open failed"));
424
+ });
425
+ }
426
+ function migrateV1Events(tx, events) {
427
+ const byId = new Map(events.map((e) => [e.id.toLowerCase(), e]));
428
+ const deletion = new DeletionState();
429
+ const dels = events.filter((e) => e.kind === Kind.EventDeletion).sort((a, b) => a.created_at - b.created_at || a.id.localeCompare(b.id));
430
+ for (const del of dels) {
431
+ const plan = planDeletion(del, (id) => byId.get(id));
432
+ deletion.absorb(plan);
433
+ for (const c of plan.coordinates) for (const ev of events) {
434
+ if (ev.kind === Kind.EventDeletion) continue;
435
+ if (eventAddress(ev) === c.key && ev.created_at <= c.until) deletion.ids.add(ev.id.toLowerCase());
436
+ }
437
+ }
438
+ const eventsStore = tx.objectStore(EVENTS);
439
+ const tagRefs = tx.objectStore(TAG_REFS);
440
+ const addresses = tx.objectStore(ADDRESSES);
441
+ const tombstones = tx.objectStore(TOMBSTONES);
442
+ for (const id of deletion.ids) tombstones.put({
443
+ key: `id:${id}`,
444
+ type: "id"
445
+ });
446
+ for (const [id, pubkey] of deletion.pending) tombstones.put({
447
+ key: `pending:${id}`,
448
+ type: "pending",
449
+ pubkey: pubkey.toLowerCase()
450
+ });
451
+ for (const [key, until] of deletion.coordinates) tombstones.put({
452
+ key: `coord:${key}`,
453
+ type: "coord",
454
+ until
455
+ });
456
+ const winners = /* @__PURE__ */ new Map();
457
+ for (const event of events) {
458
+ const stored = normalizeEvent(event);
459
+ if (deletion.ids.has(stored.id) || deletion.covers(stored)) {
460
+ eventsStore.delete(event.id);
461
+ continue;
462
+ }
463
+ if (stored.id !== event.id) eventsStore.delete(event.id);
464
+ if (stored !== event) eventsStore.put(stored);
465
+ writeTagRefs(tagRefs, stored);
466
+ const addr = eventAddress(stored);
467
+ if (addr) {
468
+ const prev = winners.get(addr);
469
+ if (!prev || isReplaceableWinner(stored, prev)) winners.set(addr, stored);
470
+ }
471
+ }
472
+ for (const [address, event] of winners) addresses.put({
473
+ address,
474
+ id: event.id,
475
+ created_at: event.created_at
476
+ });
477
+ }
478
+ function compactSupersededReplaceables(tx) {
479
+ const eventsStore = tx.objectStore(EVENTS);
480
+ const addressesStore = tx.objectStore(ADDRESSES);
481
+ const evReq = eventsStore.getAll();
482
+ const addrReq = addressesStore.getAll();
483
+ let events;
484
+ let addressRows;
485
+ const run = () => {
486
+ if (events === void 0 || addressRows === void 0) return;
487
+ const byAddr = /* @__PURE__ */ new Map();
488
+ for (const row of addressRows) byAddr.set(row.address, row);
489
+ const kept = [];
490
+ for (const event of events) {
491
+ const addr = eventAddress(event);
492
+ if (addr) {
493
+ const row = byAddr.get(addr);
494
+ if (!row || row.id !== event.id) {
495
+ deleteStoredEvent(tx, event, row);
496
+ continue;
497
+ }
498
+ }
499
+ kept.push(event);
500
+ }
501
+ addressesStore.clear();
502
+ for (const event of kept) {
503
+ const addr = eventAddress(event);
504
+ if (!addr) continue;
505
+ addressesStore.put({
506
+ address: addr,
507
+ id: event.id,
508
+ created_at: event.created_at
509
+ });
510
+ }
511
+ };
512
+ evReq.onsuccess = () => {
513
+ events = evReq.result ?? [];
514
+ run();
515
+ };
516
+ addrReq.onsuccess = () => {
517
+ addressRows = addrReq.result ?? [];
518
+ run();
519
+ };
520
+ }
521
+ //#endregion
522
+ //#region src/storage/indexeddb.ts
523
+ /**
524
+ * Browser IndexedDB event store with the same replaceable / deletion semantics
525
+ * as {@link MemoryEventStore}. Requires a DOM IndexedDB implementation.
526
+ *
527
+ * `open` loads every tombstone and address row into memory. Fine at 10^4 events;
528
+ * tens of MB at 10^5 addressables.
529
+ */
530
+ var IndexedDbEventStore = class IndexedDbEventStore {
531
+ #dbName;
532
+ #db;
533
+ #deletion = new DeletionState();
534
+ #replaceable = /* @__PURE__ */ new Map();
535
+ /** Serializes writes so abort restore cannot roll back a committed sibling tx. */
536
+ #writeTail = Promise.resolve();
537
+ constructor(opts = {}) {
538
+ this.#dbName = opts.dbName ?? "@qntx/nostr";
539
+ }
540
+ static isAvailable() {
541
+ return typeof globalThis.indexedDB !== "undefined";
542
+ }
543
+ async open() {
544
+ if (this.#db) return;
545
+ if (!IndexedDbEventStore.isAvailable()) throw new StorageError("IndexedDB is not available in this environment");
546
+ this.#db = await openDb(this.#dbName);
547
+ await this.#loadCaches();
548
+ }
549
+ async #ensure() {
550
+ await this.open();
551
+ return this.#db;
552
+ }
553
+ /** Loads every tombstone and address row into RAM. Tens of MB at 10^5 addressables. */
554
+ async #loadCaches() {
555
+ const tx = this.#db.transaction([TOMBSTONES, ADDRESSES], "readonly");
556
+ const done = txDone(tx);
557
+ this.#deletion.clear();
558
+ this.#replaceable.clear();
559
+ const tombs = await reqOf(tx.objectStore(TOMBSTONES).getAll());
560
+ this.#deletion.absorb(tombstonesToPlan(tombs ?? []));
561
+ await walkCursor(tx.objectStore(ADDRESSES), void 0, "next", (cursor) => {
562
+ const row = cursor.value;
563
+ this.#replaceable.set(row.address, row.id);
564
+ return false;
565
+ });
566
+ await done;
567
+ }
568
+ #enqueueWrite(op) {
569
+ const run = this.#writeTail.then(op, op);
570
+ this.#writeTail = run.then(() => void 0, () => void 0);
571
+ return run;
572
+ }
573
+ async put(event) {
574
+ const [result] = await this.putMany([event]);
575
+ return result;
576
+ }
577
+ /** One readwrite transaction; abort rejects the whole batch with no partial persist. */
578
+ async putMany(events) {
579
+ if (events.length === 0) return [];
580
+ return this.#enqueueWrite(() => this.#putManyLocked(events));
581
+ }
582
+ async #putManyLocked(events) {
583
+ let db;
584
+ try {
585
+ db = await this.#ensure();
586
+ } catch (err) {
587
+ throw toStorageError(err);
588
+ }
589
+ const tx = db.transaction(WRITE_STORES, "readwrite");
590
+ const txOutcome = new Promise((resolve, reject) => {
591
+ tx.oncomplete = () => resolve();
592
+ tx.onerror = () => reject(tx.error ?? new StorageError("IndexedDB transaction failed"));
593
+ tx.onabort = () => reject(tx.error ?? new StorageError("IndexedDB transaction aborted"));
594
+ }).then(() => void 0, (err) => err);
595
+ const cache = this.#snapshotCaches();
596
+ try {
597
+ const results = await this.#putAllInTx(tx, events);
598
+ const txErr = await txOutcome;
599
+ if (txErr !== void 0) throw txErr;
600
+ return results;
601
+ } catch (err) {
602
+ this.#restoreCaches(cache);
603
+ try {
604
+ tx.abort();
605
+ } catch {}
606
+ await txOutcome;
607
+ throw toStorageError(err);
608
+ }
609
+ }
610
+ /** Sequential awaits so the tx stays alive between events. */
611
+ async #putAllInTx(tx, batch) {
612
+ const eventsStore = tx.objectStore(EVENTS);
613
+ const addressesStore = tx.objectStore(ADDRESSES);
614
+ const results = [];
615
+ for (const raw of batch) {
616
+ const event = normalizeEvent(raw);
617
+ const byId = /* @__PURE__ */ new Map();
618
+ const existing = await reqOf(eventsStore.get(event.id));
619
+ if (existing) byId.set(existing.id, existing);
620
+ if (event.kind === Kind.EventDeletion) for (const tag of event.tags) {
621
+ if (tag[0] !== "e" || tag[1] === void 0) continue;
622
+ const got = await reqOf(eventsStore.get(tag[1].toLowerCase()));
623
+ if (got) byId.set(got.id, got);
624
+ }
625
+ const addrRows = /* @__PURE__ */ new Map();
626
+ const ownAddr = eventAddress(event);
627
+ if (ownAddr) {
628
+ const row = await reqOf(addressesStore.get(ownAddr));
629
+ if (row) addrRows.set(ownAddr, {
630
+ id: row.id,
631
+ created_at: row.created_at
632
+ });
633
+ }
634
+ if (event.kind === Kind.EventDeletion) for (const tag of event.tags) {
635
+ if (tag[0] !== "a" || !tag[1]) continue;
636
+ const coord = parseEventAddress(tag[1]);
637
+ if (!coord) continue;
638
+ const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;
639
+ if (addrRows.has(key)) continue;
640
+ const row = await reqOf(addressesStore.get(key));
641
+ if (row) addrRows.set(key, {
642
+ id: row.id,
643
+ created_at: row.created_at
644
+ });
645
+ }
646
+ const d = decidePut(event, {
647
+ deletion: this.#deletion,
648
+ getById: (id) => byId.get(id),
649
+ getReplaceable: (address) => addrRows.get(address)
650
+ });
651
+ if (d.action === "delete") {
652
+ const remove = /* @__PURE__ */ new Set([...d.plan.removeIds, ...d.coordIds]);
653
+ for (const id of remove) await this.#deleteEventRows(tx, id);
654
+ } else if (d.action === "insert" && d.replaceId) await this.#deleteEventRows(tx, d.replaceId);
655
+ results.push(applyPutIndexedDb(tx, {
656
+ deletion: this.#deletion,
657
+ replaceable: this.#replaceable
658
+ }, d));
659
+ }
660
+ return results;
661
+ }
662
+ #snapshotCaches() {
663
+ return {
664
+ replaceable: new Map(this.#replaceable),
665
+ ids: new Set(this.#deletion.ids),
666
+ pending: new Map(this.#deletion.pending),
667
+ coordinates: new Map(this.#deletion.coordinates)
668
+ };
669
+ }
670
+ #restoreCaches(snap) {
671
+ this.#replaceable = snap.replaceable;
672
+ this.#deletion.ids.clear();
673
+ for (const id of snap.ids) this.#deletion.ids.add(id);
674
+ this.#deletion.pending.clear();
675
+ for (const [k, v] of snap.pending) this.#deletion.pending.set(k, v);
676
+ this.#deletion.coordinates.clear();
677
+ for (const [k, v] of snap.coordinates) this.#deletion.coordinates.set(k, v);
678
+ }
679
+ async get(id) {
680
+ const key = id.toLowerCase();
681
+ if (this.#deletion.ids.has(key)) return void 0;
682
+ const tx = (await this.#ensure()).transaction(EVENTS, "readonly");
683
+ const done = txDone(tx);
684
+ const event = await reqOf(tx.objectStore(EVENTS).get(key));
685
+ await done;
686
+ return event;
687
+ }
688
+ async query(filters) {
689
+ const tx = (await this.#ensure()).transaction([EVENTS, TAG_REFS], "readonly");
690
+ const done = txDone(tx);
691
+ const seen = /* @__PURE__ */ new Set();
692
+ const events = [];
693
+ for (const filter of filters) {
694
+ const matched = await this.#queryOne(tx, filter);
695
+ for (const event of matched) {
696
+ if (seen.has(event.id)) continue;
697
+ seen.add(event.id);
698
+ events.push(event);
699
+ }
700
+ }
701
+ await done;
702
+ sortEvents(events);
703
+ return events;
704
+ }
705
+ async count(filters) {
706
+ const tx = (await this.#ensure()).transaction([EVENTS, TAG_REFS], "readonly");
707
+ const done = txDone(tx);
708
+ const seen = /* @__PURE__ */ new Set();
709
+ for (const filter of filters) {
710
+ const local = /* @__PURE__ */ new Set();
711
+ await this.#scan(tx, filter, (event) => {
712
+ local.add(event.id);
713
+ return filter.limit !== void 0 && local.size >= filter.limit;
714
+ });
715
+ for (const id of local) seen.add(id);
716
+ }
717
+ await done;
718
+ return seen.size;
719
+ }
720
+ async negentropyItems(filter) {
721
+ const tx = (await this.#ensure()).transaction([EVENTS, TAG_REFS], "readonly");
722
+ const done = txDone(tx);
723
+ const items = [];
724
+ await this.#scan(tx, filter, (event) => {
725
+ items.push({
726
+ id: event.id,
727
+ created_at: event.created_at
728
+ });
729
+ return filter.limit !== void 0 && items.length >= filter.limit;
730
+ });
731
+ await done;
732
+ items.sort(itemCompare);
733
+ return items;
734
+ }
735
+ #acceptHit(filter, event) {
736
+ if (this.#deletion.ids.has(event.id)) return false;
737
+ if (this.#deletion.covers(event)) return false;
738
+ return matchFilter(filter, event);
739
+ }
740
+ async #queryOne(tx, filter) {
741
+ const matched = [];
742
+ await this.#scan(tx, filter, (event) => {
743
+ matched.push(event);
744
+ return filter.limit !== void 0 && matched.length >= filter.limit;
745
+ });
746
+ sortEvents(matched);
747
+ return matched;
748
+ }
749
+ #scan(tx, filter, take) {
750
+ return scanFilter(tx, filter, (event) => this.#acceptHit(filter, event), take);
751
+ }
752
+ async getOutboxBound(pubkey, kind) {
753
+ try {
754
+ const tx = (await this.#ensure()).transaction(OUTBOX_BOUNDS, "readonly");
755
+ const done = txDone(tx);
756
+ const row = await reqOf(tx.objectStore(OUTBOX_BOUNDS).get(outboxBoundKey(pubkey, kind)));
757
+ await done;
758
+ if (row && typeof row.oldest === "number" && typeof row.newest === "number") return {
759
+ oldest: row.oldest,
760
+ newest: row.newest
761
+ };
762
+ return await this.#deriveOutboxBound(pubkey, kind);
763
+ } catch (err) {
764
+ throw toStorageError(err);
765
+ }
766
+ }
767
+ async setOutboxBound(pubkey, kind, bound) {
768
+ return this.#enqueueWrite(() => this.#setOutboxBoundLocked(pubkey, kind, bound));
769
+ }
770
+ async #setOutboxBoundLocked(pubkey, kind, bound) {
771
+ try {
772
+ const tx = (await this.#ensure()).transaction(OUTBOX_BOUNDS, "readwrite");
773
+ const done = txDone(tx);
774
+ tx.objectStore(OUTBOX_BOUNDS).put({
775
+ key: outboxBoundKey(pubkey, kind),
776
+ oldest: bound.oldest,
777
+ newest: bound.newest
778
+ });
779
+ await done;
780
+ } catch (err) {
781
+ throw toStorageError(err);
782
+ }
783
+ }
784
+ async remove(ids) {
785
+ return this.#enqueueWrite(() => this.#removeLocked(ids));
786
+ }
787
+ async #removeLocked(ids) {
788
+ const tx = (await this.#ensure()).transaction(WRITE_STORES, "readwrite");
789
+ const tombstones = tx.objectStore(TOMBSTONES);
790
+ const done = txDone(tx);
791
+ let n = 0;
792
+ for (const raw of ids) {
793
+ const id = raw.toLowerCase();
794
+ if (await this.#deleteEventRows(tx, id)) n += 1;
795
+ tombstones.put({
796
+ key: `id:${id}`,
797
+ type: "id"
798
+ });
799
+ tombstones.delete(`pending:${id}`);
800
+ this.#deletion.ids.add(id);
801
+ this.#deletion.pending.delete(id);
802
+ }
803
+ await done;
804
+ return n;
805
+ }
806
+ async clear() {
807
+ return this.#enqueueWrite(() => this.#clearLocked());
808
+ }
809
+ async #clearLocked() {
810
+ const db = await this.#ensure();
811
+ const stores = [...WRITE_STORES, OUTBOX_BOUNDS];
812
+ const tx = db.transaction(stores, "readwrite");
813
+ const done = txDone(tx);
814
+ for (const name of stores) tx.objectStore(name).clear();
815
+ this.#deletion.clear();
816
+ this.#replaceable.clear();
817
+ await done;
818
+ }
819
+ close() {
820
+ this.#db?.close();
821
+ this.#db = void 0;
822
+ }
823
+ async #deleteEventRows(tx, id) {
824
+ const event = await reqOf(tx.objectStore(EVENTS).get(id.toLowerCase()));
825
+ if (!event) return false;
826
+ const addr = eventAddress(event);
827
+ let row;
828
+ if (addr) {
829
+ row = await reqOf(tx.objectStore(ADDRESSES).get(addr));
830
+ if (this.#replaceable.get(addr) === event.id) this.#replaceable.delete(addr);
831
+ }
832
+ deleteStoredEvent(tx, event, row);
833
+ return true;
834
+ }
835
+ async #deriveOutboxBound(pubkey, kind) {
836
+ const db = await this.#ensure();
837
+ const pk = pubkey.toLowerCase();
838
+ const tx = db.transaction(EVENTS, "readonly");
839
+ const done = txDone(tx);
840
+ const index = tx.objectStore(EVENTS).index("kind_pubkey_created_at");
841
+ const range = prefixRange([kind, pk]);
842
+ const filter = {
843
+ authors: [pk],
844
+ kinds: [kind]
845
+ };
846
+ let oldest;
847
+ let newest;
848
+ const firstLive = (direction, assign) => walkCursor(index, range, direction, (cursor) => {
849
+ const event = cursor.value;
850
+ if (!this.#acceptHit(filter, event)) return false;
851
+ assign(event.created_at);
852
+ return true;
853
+ });
854
+ await Promise.all([firstLive("next", (createdAt) => {
855
+ oldest = createdAt;
856
+ }), firstLive("prev", (createdAt) => {
857
+ newest = createdAt;
858
+ })]);
859
+ await done;
860
+ if (oldest === void 0 || newest === void 0) return void 0;
861
+ return {
862
+ oldest,
863
+ newest
864
+ };
865
+ }
866
+ };
867
+ //#endregion
868
+ export { IndexedDbEventStore as t };
869
+
870
+ //# sourceMappingURL=storage-DOy0iLRE.mjs.map