@mrkvon/ndk-cache-dexie 2.7.9-fix
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/index.d.mts +224 -0
- package/dist/index.d.ts +224 -0
- package/dist/index.js +1088 -0
- package/dist/index.mjs +1051 -0
- package/package.json +64 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { deserialize, NDKEvent as NDKEvent2, profileFromEvent } from "@nostr-dev-kit/ndk";
|
|
3
|
+
import createDebug3 from "debug";
|
|
4
|
+
import { matchFilter } from "nostr-tools";
|
|
5
|
+
|
|
6
|
+
// src/cache-module.ts
|
|
7
|
+
import createDebug from "debug";
|
|
8
|
+
import Dexie from "dexie";
|
|
9
|
+
var debug = createDebug("ndk:dexie-adapter:modules");
|
|
10
|
+
var DexieModuleCollection = class {
|
|
11
|
+
constructor(db2, tableName) {
|
|
12
|
+
this.db = db2;
|
|
13
|
+
this.tableName = tableName;
|
|
14
|
+
}
|
|
15
|
+
get table() {
|
|
16
|
+
return this.db[this.tableName];
|
|
17
|
+
}
|
|
18
|
+
async get(id) {
|
|
19
|
+
const result = await this.table.get(id);
|
|
20
|
+
return result || null;
|
|
21
|
+
}
|
|
22
|
+
async getMany(ids) {
|
|
23
|
+
const results = await this.table.where(":id").anyOf(ids).toArray();
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
async save(item) {
|
|
27
|
+
await this.table.put(item);
|
|
28
|
+
}
|
|
29
|
+
async saveMany(items) {
|
|
30
|
+
await this.table.bulkPut(items);
|
|
31
|
+
}
|
|
32
|
+
async delete(id) {
|
|
33
|
+
await this.table.delete(id);
|
|
34
|
+
}
|
|
35
|
+
async deleteMany(ids) {
|
|
36
|
+
await this.table.where(":id").anyOf(ids).delete();
|
|
37
|
+
}
|
|
38
|
+
async findBy(field, value) {
|
|
39
|
+
return await this.table.where(field).equals(value).toArray();
|
|
40
|
+
}
|
|
41
|
+
async where(conditions) {
|
|
42
|
+
let collection = this.table.toCollection();
|
|
43
|
+
for (const [field, value] of Object.entries(conditions)) {
|
|
44
|
+
collection = collection.and((item) => item[field] === value);
|
|
45
|
+
}
|
|
46
|
+
return await collection.toArray();
|
|
47
|
+
}
|
|
48
|
+
async all() {
|
|
49
|
+
return await this.table.toArray();
|
|
50
|
+
}
|
|
51
|
+
async count(conditions) {
|
|
52
|
+
if (!conditions) {
|
|
53
|
+
return await this.table.count();
|
|
54
|
+
}
|
|
55
|
+
let collection = this.table.toCollection();
|
|
56
|
+
for (const [field, value] of Object.entries(conditions)) {
|
|
57
|
+
collection = collection.and((item) => item[field] === value);
|
|
58
|
+
}
|
|
59
|
+
return await collection.count();
|
|
60
|
+
}
|
|
61
|
+
async clear() {
|
|
62
|
+
await this.table.clear();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var DexieCacheModuleManager = class {
|
|
66
|
+
constructor(dbName) {
|
|
67
|
+
this.dbName = dbName;
|
|
68
|
+
this.moduleDb = new Dexie(`${dbName}_modules`);
|
|
69
|
+
this.setupDatabase();
|
|
70
|
+
}
|
|
71
|
+
modules = /* @__PURE__ */ new Map();
|
|
72
|
+
moduleDb;
|
|
73
|
+
initialized = false;
|
|
74
|
+
setupDatabase() {
|
|
75
|
+
this.moduleDb.version(1).stores({
|
|
76
|
+
moduleMetadata: "&namespace"
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Register a cache module
|
|
81
|
+
*/
|
|
82
|
+
async registerModule(module) {
|
|
83
|
+
if (!this.moduleDb.isOpen()) {
|
|
84
|
+
await this.moduleDb.open();
|
|
85
|
+
}
|
|
86
|
+
const metadataTable = this.moduleDb.table("moduleMetadata");
|
|
87
|
+
const existingMetadata = await metadataTable.get(module.namespace);
|
|
88
|
+
const currentVersion = existingMetadata?.version || 0;
|
|
89
|
+
if (currentVersion >= module.version) {
|
|
90
|
+
debug(`Module ${module.namespace} is already at version ${currentVersion}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const currentDbVersion = this.moduleDb.verno;
|
|
94
|
+
const newDbVersion = currentDbVersion + 1;
|
|
95
|
+
this.moduleDb.close();
|
|
96
|
+
const stores = {
|
|
97
|
+
moduleMetadata: "&namespace"
|
|
98
|
+
};
|
|
99
|
+
for (const [collName, collDef] of Object.entries(module.collections)) {
|
|
100
|
+
const tableName = `${module.namespace}_${collName}`;
|
|
101
|
+
let indexString = `&${collDef.primaryKey}`;
|
|
102
|
+
if (collDef.indexes) {
|
|
103
|
+
indexString += `, ${collDef.indexes.join(", ")}`;
|
|
104
|
+
}
|
|
105
|
+
if (collDef.compoundIndexes) {
|
|
106
|
+
const compounds = collDef.compoundIndexes.map((fields) => `[${fields.join("+")}]`);
|
|
107
|
+
indexString += `, ${compounds.join(", ")}`;
|
|
108
|
+
}
|
|
109
|
+
stores[tableName] = indexString;
|
|
110
|
+
}
|
|
111
|
+
this.moduleDb.version(newDbVersion).stores(stores);
|
|
112
|
+
await this.moduleDb.open();
|
|
113
|
+
for (let version = currentVersion + 1; version <= module.version; version++) {
|
|
114
|
+
if (module.migrations[version]) {
|
|
115
|
+
debug(`Running migration ${version} for module ${module.namespace}`);
|
|
116
|
+
const context = {
|
|
117
|
+
fromVersion: currentVersion,
|
|
118
|
+
toVersion: version,
|
|
119
|
+
async getCollection(name) {
|
|
120
|
+
return new DexieModuleCollection(this.moduleDb, `${module.namespace}_${name}`);
|
|
121
|
+
},
|
|
122
|
+
async createCollection(name, definition) {
|
|
123
|
+
debug(`Collection ${name} created during schema update`);
|
|
124
|
+
},
|
|
125
|
+
async deleteCollection(name) {
|
|
126
|
+
debug(`Collection deletion requires database recreation`);
|
|
127
|
+
},
|
|
128
|
+
async addIndex(collection, field) {
|
|
129
|
+
debug(`Index addition requires database recreation`);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
await module.migrations[version](context);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
await metadataTable.put({
|
|
136
|
+
namespace: module.namespace,
|
|
137
|
+
version: module.version,
|
|
138
|
+
lastMigration: Date.now(),
|
|
139
|
+
collections: Object.keys(module.collections)
|
|
140
|
+
});
|
|
141
|
+
this.modules.set(module.namespace, module);
|
|
142
|
+
debug(`Module ${module.namespace} registered at version ${module.version}`);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get a collection from a module
|
|
146
|
+
*/
|
|
147
|
+
async getModuleCollection(namespace, collection) {
|
|
148
|
+
if (!this.moduleDb.isOpen()) {
|
|
149
|
+
await this.moduleDb.open();
|
|
150
|
+
}
|
|
151
|
+
const tableName = `${namespace}_${collection}`;
|
|
152
|
+
const table = this.moduleDb[tableName];
|
|
153
|
+
if (!table) {
|
|
154
|
+
const metadata = await this.moduleDb.table("moduleMetadata").get(namespace);
|
|
155
|
+
if (!metadata) {
|
|
156
|
+
throw new Error(`Module ${namespace} not registered`);
|
|
157
|
+
}
|
|
158
|
+
throw new Error(`Collection ${collection} not found in module ${namespace}`);
|
|
159
|
+
}
|
|
160
|
+
return new DexieModuleCollection(this.moduleDb, tableName);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Check if a module is registered
|
|
164
|
+
*/
|
|
165
|
+
hasModule(namespace) {
|
|
166
|
+
return this.modules.has(namespace);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get the current version of a module
|
|
170
|
+
*/
|
|
171
|
+
async getModuleVersion(namespace) {
|
|
172
|
+
if (!this.moduleDb.isOpen()) {
|
|
173
|
+
await this.moduleDb.open();
|
|
174
|
+
}
|
|
175
|
+
const metadata = await this.moduleDb.table("moduleMetadata").get(namespace);
|
|
176
|
+
return metadata?.version || 0;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// src/caches/event-tags.ts
|
|
181
|
+
async function eventTagsWarmUp(cacheHandler, eventTags) {
|
|
182
|
+
const array = await eventTags.limit(cacheHandler.maxSize).toArray();
|
|
183
|
+
for (const event of array) {
|
|
184
|
+
cacheHandler.add(event.tagValue, event.eventId, false);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
var eventTagsDump = (eventTags, debug2) => {
|
|
188
|
+
return async (dirtyKeys, cache) => {
|
|
189
|
+
const entries = [];
|
|
190
|
+
for (const tagValue of dirtyKeys) {
|
|
191
|
+
const eventIds = cache.get(tagValue);
|
|
192
|
+
if (eventIds) {
|
|
193
|
+
for (const eventId of eventIds) entries.push({ tagValue, eventId });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (entries.length > 0) {
|
|
197
|
+
debug2(`Saving ${entries.length} events cache entries to database`);
|
|
198
|
+
await eventTags.bulkPut(entries);
|
|
199
|
+
}
|
|
200
|
+
dirtyKeys.clear();
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// src/caches/events.ts
|
|
205
|
+
async function eventsWarmUp(cacheHandler, events) {
|
|
206
|
+
const array = await events.limit(cacheHandler.maxSize).toArray();
|
|
207
|
+
for (const event of array) {
|
|
208
|
+
cacheHandler.set(event.id, event, false);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
var eventsDump = (events, debug2) => {
|
|
212
|
+
return async (dirtyKeys, cache) => {
|
|
213
|
+
const entries = [];
|
|
214
|
+
for (const event of dirtyKeys) {
|
|
215
|
+
const entry = cache.get(event);
|
|
216
|
+
if (entry) entries.push(entry);
|
|
217
|
+
}
|
|
218
|
+
if (entries.length > 0) {
|
|
219
|
+
debug2(`Saving ${entries.length} events cache entries to database`);
|
|
220
|
+
await events.bulkPut(entries);
|
|
221
|
+
}
|
|
222
|
+
dirtyKeys.clear();
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// src/caches/nip05.ts
|
|
227
|
+
async function nip05WarmUp(cacheHandler, nip05s) {
|
|
228
|
+
const array = await nip05s.limit(cacheHandler.maxSize).toArray();
|
|
229
|
+
for (const nip05 of array) {
|
|
230
|
+
cacheHandler.set(nip05.nip05, nip05, false);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
var nip05Dump = (nip05s, debug2) => {
|
|
234
|
+
return async (dirtyKeys, cache) => {
|
|
235
|
+
const entries = [];
|
|
236
|
+
for (const nip05 of dirtyKeys) {
|
|
237
|
+
const entry = cache.get(nip05);
|
|
238
|
+
if (entry) {
|
|
239
|
+
entries.push({
|
|
240
|
+
nip05,
|
|
241
|
+
...entry
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (entries.length) {
|
|
246
|
+
debug2(`Saving ${entries.length} NIP-05 cache entries to database`);
|
|
247
|
+
await nip05s.bulkPut(entries);
|
|
248
|
+
}
|
|
249
|
+
dirtyKeys.clear();
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// src/db.ts
|
|
254
|
+
import Dexie2 from "dexie";
|
|
255
|
+
var Database = class extends Dexie2 {
|
|
256
|
+
profiles;
|
|
257
|
+
events;
|
|
258
|
+
eventTags;
|
|
259
|
+
nip05;
|
|
260
|
+
lnurl;
|
|
261
|
+
relayStatus;
|
|
262
|
+
unpublishedEvents;
|
|
263
|
+
eventRelays;
|
|
264
|
+
decryptedEvents;
|
|
265
|
+
constructor(name) {
|
|
266
|
+
super(name);
|
|
267
|
+
this.version(19).stores({
|
|
268
|
+
profiles: "&pubkey",
|
|
269
|
+
events: "&id, kind",
|
|
270
|
+
eventTags: "[tagValue+eventId], tagValue",
|
|
271
|
+
nip05: "&nip05",
|
|
272
|
+
lnurl: "&pubkey",
|
|
273
|
+
relayStatus: "&url",
|
|
274
|
+
unpublishedEvents: "&id",
|
|
275
|
+
eventRelays: "[eventId+relayUrl], eventId",
|
|
276
|
+
decryptedEvents: "&id"
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
var db;
|
|
281
|
+
function createDatabase(name) {
|
|
282
|
+
db = new Database(name);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/caches/profiles.ts
|
|
286
|
+
import createDebug2 from "debug";
|
|
287
|
+
var d = createDebug2("ndk:dexie-adapter:profiles");
|
|
288
|
+
async function profilesWarmUp(cacheHandler, profiles) {
|
|
289
|
+
const array = await profiles.limit(cacheHandler.maxSize).toArray();
|
|
290
|
+
for (const user of array) {
|
|
291
|
+
const obj = user;
|
|
292
|
+
cacheHandler.set(user.pubkey, obj, false);
|
|
293
|
+
}
|
|
294
|
+
d("Loaded %d profiles from database", cacheHandler.size());
|
|
295
|
+
}
|
|
296
|
+
var profilesDump = (profiles, debug2) => {
|
|
297
|
+
return async (dirtyKeys, cache) => {
|
|
298
|
+
const entries = [];
|
|
299
|
+
for (const pubkey of dirtyKeys) {
|
|
300
|
+
const entry = cache.get(pubkey);
|
|
301
|
+
if (entry) {
|
|
302
|
+
entries.push(entry);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (entries.length) {
|
|
306
|
+
debug2(`Saving ${entries.length} users to database`);
|
|
307
|
+
await profiles.bulkPut(entries);
|
|
308
|
+
}
|
|
309
|
+
dirtyKeys.clear();
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// src/caches/relay-info.ts
|
|
314
|
+
async function relayInfoWarmUp(cacheHandler, relayStatus) {
|
|
315
|
+
const array = await relayStatus.limit(cacheHandler.maxSize).toArray();
|
|
316
|
+
for (const entry of array) {
|
|
317
|
+
cacheHandler.set(
|
|
318
|
+
entry.url,
|
|
319
|
+
{
|
|
320
|
+
url: entry.url,
|
|
321
|
+
updatedAt: entry.updatedAt,
|
|
322
|
+
lastConnectedAt: entry.lastConnectedAt,
|
|
323
|
+
dontConnectBefore: entry.dontConnectBefore
|
|
324
|
+
},
|
|
325
|
+
false
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
var relayInfoDump = (relayStatus, debug2) => {
|
|
330
|
+
return async (dirtyKeys, cache) => {
|
|
331
|
+
const entries = [];
|
|
332
|
+
for (const url of dirtyKeys) {
|
|
333
|
+
const info = cache.get(url);
|
|
334
|
+
if (info) {
|
|
335
|
+
entries.push({
|
|
336
|
+
url,
|
|
337
|
+
updatedAt: info.updatedAt,
|
|
338
|
+
lastConnectedAt: info.lastConnectedAt,
|
|
339
|
+
dontConnectBefore: info.dontConnectBefore
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (entries.length > 0) {
|
|
344
|
+
debug2(`Saving ${entries.length} relay status cache entries to database`);
|
|
345
|
+
await relayStatus.bulkPut(entries);
|
|
346
|
+
}
|
|
347
|
+
dirtyKeys.clear();
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// src/caches/unpublished-events.ts
|
|
352
|
+
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
|
353
|
+
var WRITE_STATUS_THRESHOLD = 3;
|
|
354
|
+
async function unpublishedEventsWarmUp(cacheHandler, unpublishedEvents) {
|
|
355
|
+
await unpublishedEvents.each((unpublishedEvent) => {
|
|
356
|
+
cacheHandler.set(unpublishedEvent.event.id, unpublishedEvent, false);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function unpublishedEventsDump(unpublishedEvents, debug2) {
|
|
360
|
+
return async (dirtyKeys, cache) => {
|
|
361
|
+
const entries = [];
|
|
362
|
+
for (const eventId of dirtyKeys) {
|
|
363
|
+
const entry = cache.get(eventId);
|
|
364
|
+
if (entry) {
|
|
365
|
+
entries.push(entry);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (entries.length > 0) {
|
|
369
|
+
debug2(`Saving ${entries.length} unpublished events cache entries to database`);
|
|
370
|
+
await unpublishedEvents.bulkPut(entries);
|
|
371
|
+
}
|
|
372
|
+
dirtyKeys.clear();
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
async function discardUnpublishedEvent(unpublishedEvents, eventId) {
|
|
376
|
+
await unpublishedEvents.delete(eventId);
|
|
377
|
+
}
|
|
378
|
+
async function getUnpublishedEvents(unpublishedEvents) {
|
|
379
|
+
const events = [];
|
|
380
|
+
await unpublishedEvents.each((unpublishedEvent) => {
|
|
381
|
+
events.push({
|
|
382
|
+
event: new NDKEvent(void 0, unpublishedEvent.event),
|
|
383
|
+
relays: Object.keys(unpublishedEvent.relays),
|
|
384
|
+
lastTryAt: unpublishedEvent.lastTryAt
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
return events;
|
|
388
|
+
}
|
|
389
|
+
function addUnpublishedEvent(event, relays) {
|
|
390
|
+
const r = {};
|
|
391
|
+
relays.forEach((url) => r[url] = false);
|
|
392
|
+
this.unpublishedEvents.set(event.id, { id: event.id, event: event.rawEvent(), relays: r });
|
|
393
|
+
this.setEvent(event, [], void 0).catch((e) => {
|
|
394
|
+
console.error("[addUnpublishedEvent] Failed to store event in main table:", e);
|
|
395
|
+
});
|
|
396
|
+
const onPublished = (relay) => {
|
|
397
|
+
const url = relay.url;
|
|
398
|
+
const existingEntry = this.unpublishedEvents.get(event.id);
|
|
399
|
+
if (!existingEntry) {
|
|
400
|
+
event.off("publushed", onPublished);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
existingEntry.relays[url] = true;
|
|
404
|
+
this.unpublishedEvents.set(event.id, existingEntry);
|
|
405
|
+
const successWrites = Object.values(existingEntry.relays).filter((v) => v).length;
|
|
406
|
+
const unsuccessWrites = Object.values(existingEntry.relays).length - successWrites;
|
|
407
|
+
if (successWrites >= WRITE_STATUS_THRESHOLD || unsuccessWrites === 0) {
|
|
408
|
+
this.unpublishedEvents.delete(event.id);
|
|
409
|
+
event.off("published", onPublished);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
event.on("published", onPublished);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/caches/zapper.ts
|
|
416
|
+
async function zapperWarmUp(cacheHandler, lnurls) {
|
|
417
|
+
const array = await lnurls.limit(cacheHandler.maxSize).toArray();
|
|
418
|
+
for (const lnurl of array) {
|
|
419
|
+
cacheHandler.set(lnurl.pubkey, { document: lnurl.document, fetchedAt: lnurl.fetchedAt }, false);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
var zapperDump = (lnurls, debug2) => {
|
|
423
|
+
return async (dirtyKeys, cache) => {
|
|
424
|
+
const entries = [];
|
|
425
|
+
for (const pubkey of dirtyKeys) {
|
|
426
|
+
const entry = cache.get(pubkey);
|
|
427
|
+
if (entry) {
|
|
428
|
+
entries.push({
|
|
429
|
+
pubkey,
|
|
430
|
+
...entry
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (entries.length) {
|
|
435
|
+
debug2(`Saving ${entries.length} zapper cache entries to database`);
|
|
436
|
+
await lnurls.bulkPut(entries);
|
|
437
|
+
}
|
|
438
|
+
dirtyKeys.clear();
|
|
439
|
+
};
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// src/lru-cache.ts
|
|
443
|
+
import { LRUCache } from "typescript-lru-cache";
|
|
444
|
+
var CacheHandler = class {
|
|
445
|
+
cache;
|
|
446
|
+
dirtyKeys = /* @__PURE__ */ new Set();
|
|
447
|
+
options;
|
|
448
|
+
debug;
|
|
449
|
+
indexes;
|
|
450
|
+
isSet = false;
|
|
451
|
+
maxSize = 0;
|
|
452
|
+
constructor(options) {
|
|
453
|
+
this.debug = options.debug;
|
|
454
|
+
this.options = options;
|
|
455
|
+
this.maxSize = options.maxSize;
|
|
456
|
+
if (options.maxSize > 0) {
|
|
457
|
+
this.cache = new LRUCache({ maxSize: options.maxSize });
|
|
458
|
+
setInterval(() => this.dump().catch(console.error), 1e3 * 10);
|
|
459
|
+
}
|
|
460
|
+
this.indexes = /* @__PURE__ */ new Map();
|
|
461
|
+
}
|
|
462
|
+
getSet(key) {
|
|
463
|
+
return this.cache?.get(key);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Get all entries that match the filter.
|
|
467
|
+
*/
|
|
468
|
+
getAllWithFilter(filter) {
|
|
469
|
+
const ret = /* @__PURE__ */ new Map();
|
|
470
|
+
this.cache?.forEach((val, key) => {
|
|
471
|
+
if (filter(key, val)) {
|
|
472
|
+
ret.set(key, val);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
return ret;
|
|
476
|
+
}
|
|
477
|
+
get(key) {
|
|
478
|
+
return this.cache?.get(key);
|
|
479
|
+
}
|
|
480
|
+
async getWithFallback(key, table) {
|
|
481
|
+
let entry = this.get(key);
|
|
482
|
+
if (!entry) {
|
|
483
|
+
entry = await table.get(key);
|
|
484
|
+
if (entry) {
|
|
485
|
+
this.set(key, entry);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return entry;
|
|
489
|
+
}
|
|
490
|
+
async getManyWithFallback(keys, table) {
|
|
491
|
+
const entries = [];
|
|
492
|
+
const missingKeys = [];
|
|
493
|
+
for (const key of keys) {
|
|
494
|
+
const entry = this.get(key);
|
|
495
|
+
if (entry) entries.push(entry);
|
|
496
|
+
else missingKeys.push(key);
|
|
497
|
+
}
|
|
498
|
+
if (entries.length > 0) {
|
|
499
|
+
this.debug(`Cache hit for keys ${entries.length} and miss for ${missingKeys.length} keys`);
|
|
500
|
+
}
|
|
501
|
+
if (missingKeys.length > 0) {
|
|
502
|
+
const startTime = Date.now();
|
|
503
|
+
const missingEntries = await table.bulkGet(missingKeys);
|
|
504
|
+
const endTime = Date.now();
|
|
505
|
+
let foundKeys = 0;
|
|
506
|
+
for (const entry of missingEntries) {
|
|
507
|
+
if (entry) {
|
|
508
|
+
this.set(entry.id, entry);
|
|
509
|
+
entries.push(entry);
|
|
510
|
+
foundKeys++;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
this.debug(
|
|
514
|
+
`Time spent querying database: ${endTime - startTime}ms for ${missingKeys.length} keys, which added ${foundKeys} entries to the cache`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
return entries;
|
|
518
|
+
}
|
|
519
|
+
add(key, value, dirty = true) {
|
|
520
|
+
const existing = this.get(key) ?? /* @__PURE__ */ new Set();
|
|
521
|
+
existing.add(value);
|
|
522
|
+
this.cache?.set(key, existing);
|
|
523
|
+
if (dirty) this.dirtyKeys.add(key);
|
|
524
|
+
}
|
|
525
|
+
set(key, value, dirty = true) {
|
|
526
|
+
this.cache?.set(key, value);
|
|
527
|
+
if (dirty) this.dirtyKeys.add(key);
|
|
528
|
+
for (const [attribute, index] of this.indexes.entries()) {
|
|
529
|
+
const indexKey = value[attribute];
|
|
530
|
+
if (indexKey) {
|
|
531
|
+
const indexValue = index.get(indexKey) || /* @__PURE__ */ new Set();
|
|
532
|
+
indexValue.add(key);
|
|
533
|
+
index.set(indexKey, indexValue);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
size() {
|
|
538
|
+
return this.cache?.size || 0;
|
|
539
|
+
}
|
|
540
|
+
delete(key) {
|
|
541
|
+
this.cache?.delete(key);
|
|
542
|
+
this.dirtyKeys.add(key);
|
|
543
|
+
}
|
|
544
|
+
async dump() {
|
|
545
|
+
if (this.dirtyKeys.size > 0 && this.cache) {
|
|
546
|
+
await this.options.dump(this.dirtyKeys, this.cache);
|
|
547
|
+
this.dirtyKeys.clear();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
addIndex(attribute) {
|
|
551
|
+
this.indexes.set(attribute, new LRUCache({ maxSize: this.options.maxSize }));
|
|
552
|
+
}
|
|
553
|
+
getFromIndex(index, key) {
|
|
554
|
+
const ret = /* @__PURE__ */ new Set();
|
|
555
|
+
const indexValues = this.indexes.get(index);
|
|
556
|
+
if (indexValues) {
|
|
557
|
+
const values = indexValues.get(key);
|
|
558
|
+
if (values) {
|
|
559
|
+
for (const key2 of values.values()) {
|
|
560
|
+
const entry = this.get(key2);
|
|
561
|
+
if (entry) ret.add(entry);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return ret;
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
// src/index.ts
|
|
570
|
+
var INDEXABLE_TAGS_LIMIT = 10;
|
|
571
|
+
var NDKCacheAdapterDexie = class {
|
|
572
|
+
debug;
|
|
573
|
+
locking = false;
|
|
574
|
+
ready = false;
|
|
575
|
+
profiles;
|
|
576
|
+
zappers;
|
|
577
|
+
nip05s;
|
|
578
|
+
events;
|
|
579
|
+
eventTags;
|
|
580
|
+
relayInfo;
|
|
581
|
+
unpublishedEvents;
|
|
582
|
+
warmedUp = false;
|
|
583
|
+
warmUpPromise;
|
|
584
|
+
devMode = false;
|
|
585
|
+
saveSig;
|
|
586
|
+
_onReady;
|
|
587
|
+
moduleManager;
|
|
588
|
+
constructor(opts = {}) {
|
|
589
|
+
const dbName = opts.dbName || "ndk";
|
|
590
|
+
createDatabase(dbName);
|
|
591
|
+
this.debug = opts.debug || createDebug3("ndk:dexie-adapter");
|
|
592
|
+
this.saveSig = opts.saveSig || false;
|
|
593
|
+
this.moduleManager = new DexieCacheModuleManager(dbName);
|
|
594
|
+
this.profiles = new CacheHandler({
|
|
595
|
+
maxSize: opts.profileCacheSize || 1e5,
|
|
596
|
+
dump: profilesDump(db.profiles, this.debug),
|
|
597
|
+
debug: this.debug
|
|
598
|
+
});
|
|
599
|
+
this.zappers = new CacheHandler({
|
|
600
|
+
maxSize: opts.zapperCacheSize || 200,
|
|
601
|
+
dump: zapperDump(db.lnurl, this.debug),
|
|
602
|
+
debug: this.debug
|
|
603
|
+
});
|
|
604
|
+
this.nip05s = new CacheHandler({
|
|
605
|
+
maxSize: opts.nip05CacheSize || 1e3,
|
|
606
|
+
dump: nip05Dump(db.nip05, this.debug),
|
|
607
|
+
debug: this.debug
|
|
608
|
+
});
|
|
609
|
+
this.events = new CacheHandler({
|
|
610
|
+
maxSize: opts.eventCacheSize || 5e4,
|
|
611
|
+
dump: eventsDump(db.events, this.debug),
|
|
612
|
+
debug: this.debug
|
|
613
|
+
});
|
|
614
|
+
this.events.addIndex("pubkey");
|
|
615
|
+
this.events.addIndex("kind");
|
|
616
|
+
this.eventTags = new CacheHandler({
|
|
617
|
+
maxSize: opts.eventTagsCacheSize || 1e5,
|
|
618
|
+
dump: eventTagsDump(db.eventTags, this.debug),
|
|
619
|
+
debug: this.debug
|
|
620
|
+
});
|
|
621
|
+
this.relayInfo = new CacheHandler({
|
|
622
|
+
maxSize: 500,
|
|
623
|
+
debug: this.debug,
|
|
624
|
+
dump: relayInfoDump(db.relayStatus, this.debug)
|
|
625
|
+
});
|
|
626
|
+
this.unpublishedEvents = new CacheHandler({
|
|
627
|
+
maxSize: 5e3,
|
|
628
|
+
debug: this.debug,
|
|
629
|
+
dump: unpublishedEventsDump(db.unpublishedEvents, this.debug)
|
|
630
|
+
});
|
|
631
|
+
const profile = (label, fn) => {
|
|
632
|
+
const start = Date.now();
|
|
633
|
+
return fn().then(() => {
|
|
634
|
+
const end = Date.now();
|
|
635
|
+
this.debug(label, "took", end - start, "ms");
|
|
636
|
+
});
|
|
637
|
+
};
|
|
638
|
+
const startTime = Date.now();
|
|
639
|
+
this.warmUpPromise = Promise.allSettled([
|
|
640
|
+
profile("profilesWarmUp", () => profilesWarmUp(this.profiles, db.profiles)),
|
|
641
|
+
profile("zapperWarmUp", () => zapperWarmUp(this.zappers, db.lnurl)),
|
|
642
|
+
profile("nip05WarmUp", () => nip05WarmUp(this.nip05s, db.nip05)),
|
|
643
|
+
profile("relayInfoWarmUp", () => relayInfoWarmUp(this.relayInfo, db.relayStatus)),
|
|
644
|
+
profile(
|
|
645
|
+
"unpublishedEventsWarmUp",
|
|
646
|
+
() => unpublishedEventsWarmUp(this.unpublishedEvents, db.unpublishedEvents)
|
|
647
|
+
),
|
|
648
|
+
profile("eventsWarmUp", () => eventsWarmUp(this.events, db.events)),
|
|
649
|
+
profile("eventTagsWarmUp", () => eventTagsWarmUp(this.eventTags, db.eventTags))
|
|
650
|
+
]);
|
|
651
|
+
this.warmUpPromise.then(() => {
|
|
652
|
+
const endTime = Date.now();
|
|
653
|
+
this.warmedUp = true;
|
|
654
|
+
this.ready = true;
|
|
655
|
+
this.locking = true;
|
|
656
|
+
this.debug("Warm up completed, time", endTime - startTime, "ms");
|
|
657
|
+
if (this._onReady) this._onReady();
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
onReady(callback) {
|
|
661
|
+
this._onReady = callback;
|
|
662
|
+
}
|
|
663
|
+
async query(subscription) {
|
|
664
|
+
if (!this.warmedUp) {
|
|
665
|
+
const startTime2 = Date.now();
|
|
666
|
+
await this.warmUpPromise;
|
|
667
|
+
this.debug("froze query for", Date.now() - startTime2, "ms", subscription.filters);
|
|
668
|
+
}
|
|
669
|
+
const startTime = Date.now();
|
|
670
|
+
subscription.filters.map((filter) => this.processFilter(filter, subscription));
|
|
671
|
+
const dur = Date.now() - startTime;
|
|
672
|
+
if (dur > 100) this.debug("query took", dur, "ms", subscription.filter);
|
|
673
|
+
return [];
|
|
674
|
+
}
|
|
675
|
+
async fetchProfile(pubkey) {
|
|
676
|
+
if (!this.profiles) return null;
|
|
677
|
+
const user = await this.profiles.getWithFallback(pubkey, db.profiles);
|
|
678
|
+
return user;
|
|
679
|
+
}
|
|
680
|
+
fetchProfileSync(pubkey) {
|
|
681
|
+
if (!this.profiles) return null;
|
|
682
|
+
const user = this.profiles.get(pubkey);
|
|
683
|
+
return user;
|
|
684
|
+
}
|
|
685
|
+
async getProfiles(filter) {
|
|
686
|
+
if (!this.profiles) return;
|
|
687
|
+
const filterFn = typeof filter === "function" ? filter : (pubkey, profile) => {
|
|
688
|
+
const searchLower = filter.contains.toLowerCase();
|
|
689
|
+
const fields = filter.fields || (filter.field ? [filter.field] : ["name", "displayName", "nip05"]);
|
|
690
|
+
return fields.some((field) => {
|
|
691
|
+
const value = profile[field];
|
|
692
|
+
return typeof value === "string" && value.toLowerCase().includes(searchLower);
|
|
693
|
+
});
|
|
694
|
+
};
|
|
695
|
+
return this.profiles.getAllWithFilter(filterFn);
|
|
696
|
+
}
|
|
697
|
+
saveProfile(pubkey, profile) {
|
|
698
|
+
const existingValue = this.profiles.get(pubkey);
|
|
699
|
+
if (existingValue?.created_at && profile.created_at && existingValue.created_at >= profile.created_at) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const cachedAt = Math.floor(Date.now() / 1e3);
|
|
703
|
+
this.profiles.set(pubkey, { pubkey, ...profile, cachedAt });
|
|
704
|
+
this.debug("Saved profile for pubkey", pubkey, profile);
|
|
705
|
+
}
|
|
706
|
+
async loadNip05(nip05, maxAgeForMissing = 3600) {
|
|
707
|
+
const cache = this.nip05s?.get(nip05);
|
|
708
|
+
if (cache) {
|
|
709
|
+
if (cache.profile === null) {
|
|
710
|
+
if (cache.fetchedAt + maxAgeForMissing * 1e3 < Date.now()) return "missing";
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
try {
|
|
714
|
+
return JSON.parse(cache.profile);
|
|
715
|
+
} catch (_e) {
|
|
716
|
+
return "missing";
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
const nip = await db.nip05.get({ nip05 });
|
|
720
|
+
if (!nip) return "missing";
|
|
721
|
+
const now = Date.now();
|
|
722
|
+
if (nip.profile === null) {
|
|
723
|
+
if (nip.fetchedAt + maxAgeForMissing * 1e3 < now) return "missing";
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
try {
|
|
727
|
+
return JSON.parse(nip.profile);
|
|
728
|
+
} catch (_e) {
|
|
729
|
+
return "missing";
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
async saveNip05(nip05, profile) {
|
|
733
|
+
try {
|
|
734
|
+
const document = profile ? JSON.stringify(profile) : null;
|
|
735
|
+
this.nip05s.set(nip05, { profile: document, fetchedAt: Date.now() });
|
|
736
|
+
} catch (error) {
|
|
737
|
+
console.error("Failed to save NIP-05 profile for nip05:", nip05, error);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async loadUsersLNURLDoc(pubkey, maxAgeInSecs = 86400, maxAgeForMissing = 3600) {
|
|
741
|
+
const cache = this.zappers?.get(pubkey);
|
|
742
|
+
if (cache) {
|
|
743
|
+
if (cache.document === null) {
|
|
744
|
+
if (cache.fetchedAt + maxAgeForMissing * 1e3 < Date.now()) return "missing";
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
return JSON.parse(cache.document);
|
|
749
|
+
} catch (_e) {
|
|
750
|
+
return "missing";
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const lnurl = await db.lnurl.get({ pubkey });
|
|
754
|
+
if (!lnurl) return "missing";
|
|
755
|
+
const now = Date.now();
|
|
756
|
+
if (lnurl.fetchedAt + maxAgeInSecs * 1e3 < now) return "missing";
|
|
757
|
+
if (lnurl.document === null) {
|
|
758
|
+
if (lnurl.fetchedAt + maxAgeForMissing * 1e3 < now) return "missing";
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
return JSON.parse(lnurl.document);
|
|
763
|
+
} catch (_e) {
|
|
764
|
+
return "missing";
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async saveUsersLNURLDoc(pubkey, doc) {
|
|
768
|
+
try {
|
|
769
|
+
const document = doc ? JSON.stringify(doc) : null;
|
|
770
|
+
this.zappers?.set(pubkey, { document, fetchedAt: Date.now() });
|
|
771
|
+
} catch (error) {
|
|
772
|
+
console.error("Failed to save LNURL document for pubkey:", pubkey, error);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
processFilter(filter, subscription) {
|
|
776
|
+
const _filter = { ...filter };
|
|
777
|
+
_filter.limit = void 0;
|
|
778
|
+
const filterKeys = new Set(Object.keys(_filter || {}));
|
|
779
|
+
filterKeys.delete("since");
|
|
780
|
+
filterKeys.delete("limit");
|
|
781
|
+
filterKeys.delete("until");
|
|
782
|
+
try {
|
|
783
|
+
if (this.byNip33Query(filterKeys, filter, subscription)) return;
|
|
784
|
+
if (this.byAuthors(filter, subscription)) return;
|
|
785
|
+
if (this.byIdsQuery(filter, subscription)) return;
|
|
786
|
+
if (this.byTags(filter, subscription)) return;
|
|
787
|
+
if (this.byKinds(filterKeys, filter, subscription)) return;
|
|
788
|
+
} catch (error) {
|
|
789
|
+
console.error(error);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
async deleteEventIds(eventIds) {
|
|
793
|
+
eventIds.forEach((id) => this.events.delete(id));
|
|
794
|
+
await db.events.where({ id: eventIds }).delete();
|
|
795
|
+
}
|
|
796
|
+
addUnpublishedEvent = addUnpublishedEvent.bind(this);
|
|
797
|
+
getUnpublishedEvents = () => getUnpublishedEvents(db.unpublishedEvents);
|
|
798
|
+
discardUnpublishedEvent = (id) => discardUnpublishedEvent(db.unpublishedEvents, id);
|
|
799
|
+
async setEvent(event, _filters, relay) {
|
|
800
|
+
if (event.kind === 0) {
|
|
801
|
+
if (!this.profiles) return;
|
|
802
|
+
try {
|
|
803
|
+
const profile = profileFromEvent(event);
|
|
804
|
+
this.saveProfile(event.pubkey, profile);
|
|
805
|
+
} catch {
|
|
806
|
+
this.debug(`Failed to save profile for pubkey: ${event.pubkey}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
let addEvent = true;
|
|
810
|
+
if (event.isParamReplaceable()) {
|
|
811
|
+
const existingEvent = this.events.get(event.tagId());
|
|
812
|
+
if (existingEvent && event.created_at && existingEvent.createdAt > event.created_at) {
|
|
813
|
+
addEvent = false;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (addEvent) {
|
|
817
|
+
const eventData = {
|
|
818
|
+
id: event.tagId(),
|
|
819
|
+
pubkey: event.pubkey,
|
|
820
|
+
kind: event.kind,
|
|
821
|
+
createdAt: event.created_at ?? Date.now(),
|
|
822
|
+
relay: relay?.url,
|
|
823
|
+
event: event.serialize(this.saveSig, true)
|
|
824
|
+
};
|
|
825
|
+
if (this.saveSig && event.sig) {
|
|
826
|
+
eventData.sig = event.sig;
|
|
827
|
+
}
|
|
828
|
+
this.events.set(event.tagId(), eventData);
|
|
829
|
+
const indexableTags = getIndexableTags(event);
|
|
830
|
+
for (const tag of indexableTags) {
|
|
831
|
+
this.eventTags.add(tag[0] + tag[1], event.tagId());
|
|
832
|
+
}
|
|
833
|
+
if (relay?.url) {
|
|
834
|
+
db.eventRelays.put({
|
|
835
|
+
eventId: event.id,
|
|
836
|
+
relayUrl: relay.url,
|
|
837
|
+
seenAt: Date.now()
|
|
838
|
+
}).catch((e) => {
|
|
839
|
+
this.debug("Failed to store relay provenance", e);
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
setEventDup(event, relay) {
|
|
845
|
+
if (relay?.url) {
|
|
846
|
+
db.eventRelays.put({
|
|
847
|
+
eventId: event.id,
|
|
848
|
+
relayUrl: relay.url,
|
|
849
|
+
seenAt: Date.now()
|
|
850
|
+
}).catch((e) => {
|
|
851
|
+
this.debug("Failed to store relay provenance for duplicate event", e);
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
updateRelayStatus(url, info) {
|
|
856
|
+
const existing = this.relayInfo.get(url);
|
|
857
|
+
const merged = {
|
|
858
|
+
url,
|
|
859
|
+
updatedAt: Date.now(),
|
|
860
|
+
...existing,
|
|
861
|
+
...info,
|
|
862
|
+
metadata: {
|
|
863
|
+
...existing?.metadata,
|
|
864
|
+
...info.metadata
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
this.relayInfo.set(url, merged);
|
|
868
|
+
}
|
|
869
|
+
getRelayStatus(url) {
|
|
870
|
+
const a = this.relayInfo.get(url);
|
|
871
|
+
if (a) {
|
|
872
|
+
return {
|
|
873
|
+
lastConnectedAt: a.lastConnectedAt,
|
|
874
|
+
dontConnectBefore: a.dontConnectBefore,
|
|
875
|
+
consecutiveFailures: a.consecutiveFailures,
|
|
876
|
+
lastFailureAt: a.lastFailureAt,
|
|
877
|
+
nip11: a.nip11,
|
|
878
|
+
metadata: a.metadata
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Searches by authors
|
|
884
|
+
*/
|
|
885
|
+
byAuthors(filter, subscription) {
|
|
886
|
+
if (!filter.authors) return false;
|
|
887
|
+
let _total = 0;
|
|
888
|
+
for (const pubkey of filter.authors) {
|
|
889
|
+
let events = Array.from(this.events.getFromIndex("pubkey", pubkey));
|
|
890
|
+
if (filter.kinds) events = events.filter((e) => filter.kinds?.includes(e.kind));
|
|
891
|
+
foundEvents(subscription, events, filter);
|
|
892
|
+
_total += events.length;
|
|
893
|
+
}
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Searches by ids
|
|
898
|
+
*/
|
|
899
|
+
byIdsQuery(filter, subscription) {
|
|
900
|
+
if (filter.ids) {
|
|
901
|
+
for (const id of filter.ids) {
|
|
902
|
+
const event = this.events.get(id);
|
|
903
|
+
if (event) foundEvent(subscription, event, event.relay, filter);
|
|
904
|
+
}
|
|
905
|
+
return true;
|
|
906
|
+
}
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Searches by NIP-33
|
|
911
|
+
*/
|
|
912
|
+
byNip33Query(filterKeys, filter, subscription) {
|
|
913
|
+
const f = ["#d", "authors", "kinds"];
|
|
914
|
+
const hasAllKeys = filterKeys.size === f.length && f.every((k) => filterKeys.has(k));
|
|
915
|
+
if (hasAllKeys && filter.kinds && filter.authors) {
|
|
916
|
+
for (const kind of filter.kinds) {
|
|
917
|
+
const replaceableKind = kind >= 3e4 && kind < 4e4;
|
|
918
|
+
if (!replaceableKind) continue;
|
|
919
|
+
for (const author of filter.authors) {
|
|
920
|
+
for (const dTag of filter["#d"]) {
|
|
921
|
+
const replaceableId = `${kind}:${author}:${dTag}`;
|
|
922
|
+
const event = this.events.get(replaceableId);
|
|
923
|
+
if (event) foundEvent(subscription, event, event.relay, filter);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
929
|
+
return false;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Searches by tags and optionally filters by tags
|
|
933
|
+
*/
|
|
934
|
+
byTags(filter, subscription) {
|
|
935
|
+
const tagFilters = Object.entries(filter).filter(([filter2]) => filter2.startsWith("#") && filter2.length === 2).map(([filter2, values]) => [filter2[1], values]);
|
|
936
|
+
if (tagFilters.length === 0) return false;
|
|
937
|
+
for (const [tag, values] of tagFilters) {
|
|
938
|
+
for (const value of values) {
|
|
939
|
+
const tagValue = tag + value;
|
|
940
|
+
const eventIds = this.eventTags.getSet(tagValue);
|
|
941
|
+
if (!eventIds) continue;
|
|
942
|
+
eventIds.forEach((id) => {
|
|
943
|
+
const event = this.events.get(id);
|
|
944
|
+
if (!event) return;
|
|
945
|
+
if (!filter.kinds || filter.kinds.includes(event.kind)) {
|
|
946
|
+
foundEvent(subscription, event, event.relay, filter);
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return true;
|
|
952
|
+
}
|
|
953
|
+
byKinds(filterKeys, filter, subscription) {
|
|
954
|
+
if (!filter.kinds || filterKeys.size !== 1 || !filterKeys.has("kinds")) return false;
|
|
955
|
+
const limit = filter.limit || 500;
|
|
956
|
+
let totalEvents = 0;
|
|
957
|
+
const processedEventIds = /* @__PURE__ */ new Set();
|
|
958
|
+
const sortedKinds = [...filter.kinds].sort(
|
|
959
|
+
(a, b) => (this.events.indexes.get("kind")?.get(a)?.size || 0) - (this.events.indexes.get("kind")?.get(b)?.size || 0)
|
|
960
|
+
);
|
|
961
|
+
for (const kind of sortedKinds) {
|
|
962
|
+
const events = this.events.getFromIndex("kind", kind);
|
|
963
|
+
for (const event of events) {
|
|
964
|
+
if (processedEventIds.has(event.id)) continue;
|
|
965
|
+
processedEventIds.add(event.id);
|
|
966
|
+
foundEvent(subscription, event, event.relay, filter);
|
|
967
|
+
totalEvents++;
|
|
968
|
+
if (totalEvents >= limit) break;
|
|
969
|
+
}
|
|
970
|
+
if (totalEvents >= limit) break;
|
|
971
|
+
}
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Register a cache module with its schema and migrations
|
|
976
|
+
*/
|
|
977
|
+
async registerModule(module) {
|
|
978
|
+
await this.moduleManager.registerModule(module);
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Get a collection from a registered module
|
|
982
|
+
*/
|
|
983
|
+
async getModuleCollection(namespace, collection) {
|
|
984
|
+
return await this.moduleManager.getModuleCollection(namespace, collection);
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Get a decrypted event from the cache by its wrapper ID
|
|
988
|
+
*/
|
|
989
|
+
async getDecryptedEvent(wrapperId) {
|
|
990
|
+
try {
|
|
991
|
+
const decrypted = await db.decryptedEvents.get(wrapperId);
|
|
992
|
+
if (decrypted) {
|
|
993
|
+
const nostrEvent = JSON.parse(decrypted.event);
|
|
994
|
+
return new NDKEvent2(void 0, nostrEvent);
|
|
995
|
+
}
|
|
996
|
+
return null;
|
|
997
|
+
} catch (e) {
|
|
998
|
+
console.error(`[cache-dexie] Error getting decrypted event for wrapper ${wrapperId}:`, e);
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Add a decrypted event to the cache
|
|
1004
|
+
*/
|
|
1005
|
+
async addDecryptedEvent(wrapperId, decryptedEvent) {
|
|
1006
|
+
try {
|
|
1007
|
+
await db.decryptedEvents.put({
|
|
1008
|
+
id: wrapperId,
|
|
1009
|
+
event: JSON.stringify(decryptedEvent.rawEvent())
|
|
1010
|
+
});
|
|
1011
|
+
} catch (e) {
|
|
1012
|
+
console.error(`[cache-dexie] Error adding decrypted event for wrapper ${wrapperId}:`, e);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
function foundEvents(subscription, events, filter) {
|
|
1017
|
+
if (filter?.limit && events.length > filter.limit) {
|
|
1018
|
+
events = events.sort((a, b) => b.createdAt - a.createdAt).slice(0, filter.limit);
|
|
1019
|
+
}
|
|
1020
|
+
for (const event of events) {
|
|
1021
|
+
foundEvent(subscription, event, event.relay, filter);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function foundEvent(subscription, event, relayUrl, filter) {
|
|
1025
|
+
try {
|
|
1026
|
+
const deserializedEvent = deserialize(event.event);
|
|
1027
|
+
if (filter && !matchFilter(filter, deserializedEvent)) return;
|
|
1028
|
+
const ndkEvent = new NDKEvent2(void 0, deserializedEvent);
|
|
1029
|
+
const relay = relayUrl ? subscription.pool.getRelay(relayUrl, false) : void 0;
|
|
1030
|
+
ndkEvent.relay = relay;
|
|
1031
|
+
subscription.eventReceived(ndkEvent, relay, true);
|
|
1032
|
+
} catch (e) {
|
|
1033
|
+
console.error("failed to deserialize event", e);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function getIndexableTags(event) {
|
|
1037
|
+
const indexableTags = [];
|
|
1038
|
+
if (event.kind === 3) return [];
|
|
1039
|
+
for (const tag of event.tags) {
|
|
1040
|
+
if (tag[0].length !== 1) continue;
|
|
1041
|
+
indexableTags.push(tag);
|
|
1042
|
+
if (indexableTags.length >= INDEXABLE_TAGS_LIMIT) return [];
|
|
1043
|
+
}
|
|
1044
|
+
return indexableTags;
|
|
1045
|
+
}
|
|
1046
|
+
export {
|
|
1047
|
+
db,
|
|
1048
|
+
NDKCacheAdapterDexie as default,
|
|
1049
|
+
foundEvent,
|
|
1050
|
+
foundEvents
|
|
1051
|
+
};
|