@routstr/sdk 0.3.8 → 0.3.9
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/client/index.js +80 -25
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +80 -25
- package/dist/client/index.mjs.map +1 -1
- package/dist/discovery/index.d.mts +37 -3
- package/dist/discovery/index.d.ts +37 -3
- package/dist/discovery/index.js +231 -75
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +231 -75
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +592 -102
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +591 -103
- package/dist/index.mjs.map +1 -1
- package/dist/storage/index.d.mts +27 -1
- package/dist/storage/index.d.ts +27 -1
- package/dist/storage/index.js +281 -2
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +280 -3
- package/dist/storage/index.mjs.map +1 -1
- package/dist/wallet/index.d.mts +4 -0
- package/dist/wallet/index.d.ts +4 -0
- package/dist/wallet/index.js +11 -4
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +11 -4
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/discovery/index.mjs
CHANGED
|
@@ -32,6 +32,9 @@ var NoProvidersAvailableError = class extends Error {
|
|
|
32
32
|
this.name = "NoProvidersAvailableError";
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
+
function isBunRuntime() {
|
|
36
|
+
return typeof Bun !== "undefined";
|
|
37
|
+
}
|
|
35
38
|
var ModelManager = class _ModelManager {
|
|
36
39
|
constructor(adapter, config = {}) {
|
|
37
40
|
this.adapter = adapter;
|
|
@@ -41,6 +44,7 @@ var ModelManager = class _ModelManager {
|
|
|
41
44
|
this.excludeProviderUrls = config.excludeProviderUrls || [];
|
|
42
45
|
this.routstrPubkey = config.routstrPubkey || "4ad6fa2d16e2a9b576c863b4cf7404a70d4dc320c0c447d10ad6ff58993eacc8";
|
|
43
46
|
this.logger = (config.logger ?? consoleLogger).child("ModelManager");
|
|
47
|
+
this.eventStoreDbPath = config.eventStoreDbPath;
|
|
44
48
|
}
|
|
45
49
|
adapter;
|
|
46
50
|
cacheTTL;
|
|
@@ -50,6 +54,11 @@ var ModelManager = class _ModelManager {
|
|
|
50
54
|
routstrPubkey;
|
|
51
55
|
logger;
|
|
52
56
|
providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
|
|
57
|
+
/** Persistent event store for relay-fetched events (null if not configured/initialized) */
|
|
58
|
+
eventStore = null;
|
|
59
|
+
eventStoreDb = null;
|
|
60
|
+
eventStoreInitPromise = null;
|
|
61
|
+
eventStoreDbPath;
|
|
53
62
|
/**
|
|
54
63
|
* Get the list of bootstrapped provider base URLs
|
|
55
64
|
* @returns Array of provider base URLs
|
|
@@ -57,6 +66,104 @@ var ModelManager = class _ModelManager {
|
|
|
57
66
|
getBaseUrls() {
|
|
58
67
|
return this.adapter.getBaseUrlsList();
|
|
59
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Lazily initialize the persistent event store.
|
|
71
|
+
* Returns null if no eventStoreDbPath was provided.
|
|
72
|
+
*/
|
|
73
|
+
async ensureEventStore() {
|
|
74
|
+
if (!this.eventStoreDbPath) return null;
|
|
75
|
+
if (this.eventStore) return this.eventStore;
|
|
76
|
+
if (!this.eventStoreInitPromise) {
|
|
77
|
+
this.eventStoreInitPromise = (async () => {
|
|
78
|
+
try {
|
|
79
|
+
const db = await this.createPersistentEventDatabase();
|
|
80
|
+
this.eventStoreDb = db;
|
|
81
|
+
this.eventStore = new EventStore({ database: db });
|
|
82
|
+
this.initializeEventStoreMetadata();
|
|
83
|
+
this.logger.log(
|
|
84
|
+
`Persistent event store initialized at ${this.eventStoreDbPath}`
|
|
85
|
+
);
|
|
86
|
+
return this.eventStore;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
this.eventStoreInitPromise = null;
|
|
89
|
+
throw new Error(
|
|
90
|
+
`applesauce-sqlite with a supported SQLite driver is required for persistent Nostr event storage. Bun uses bun:sqlite; Node.js uses better-sqlite3. Install optional dependencies or omit eventStoreDbPath. (${error})`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
})();
|
|
94
|
+
}
|
|
95
|
+
return this.eventStoreInitPromise;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get the persistent event store, initializing it if configured.
|
|
99
|
+
* Returns null if no eventStoreDbPath was provided.
|
|
100
|
+
*/
|
|
101
|
+
async getEventStore() {
|
|
102
|
+
return this.ensureEventStore();
|
|
103
|
+
}
|
|
104
|
+
async createPersistentEventDatabase() {
|
|
105
|
+
if (isBunRuntime()) {
|
|
106
|
+
const { BunSqliteEventDatabase } = await import('applesauce-sqlite/bun');
|
|
107
|
+
return new BunSqliteEventDatabase(
|
|
108
|
+
this.eventStoreDbPath
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
const { BetterSqlite3EventDatabase } = await import('applesauce-sqlite/better-sqlite3');
|
|
112
|
+
return new BetterSqlite3EventDatabase(
|
|
113
|
+
this.eventStoreDbPath
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
/** Close the persistent event store database handle, if configured. */
|
|
117
|
+
closeEventStore() {
|
|
118
|
+
this.eventStoreDb?.close?.();
|
|
119
|
+
this.eventStore = null;
|
|
120
|
+
this.eventStoreDb = null;
|
|
121
|
+
this.eventStoreInitPromise = null;
|
|
122
|
+
}
|
|
123
|
+
initializeEventStoreMetadata() {
|
|
124
|
+
this.eventStoreDb?.db?.exec(
|
|
125
|
+
`CREATE TABLE IF NOT EXISTS routstr_event_cache_metadata (
|
|
126
|
+
event_id TEXT PRIMARY KEY,
|
|
127
|
+
fetched_at INTEGER NOT NULL
|
|
128
|
+
)`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
markEventFetched(event, fetchedAt = Date.now()) {
|
|
132
|
+
const db = this.eventStoreDb?.db;
|
|
133
|
+
if (!db) return;
|
|
134
|
+
db.prepare(
|
|
135
|
+
`INSERT INTO routstr_event_cache_metadata (event_id, fetched_at)
|
|
136
|
+
VALUES (?, ?)
|
|
137
|
+
ON CONFLICT(event_id) DO UPDATE SET fetched_at = excluded.fetched_at`
|
|
138
|
+
).run?.(event.id, fetchedAt);
|
|
139
|
+
}
|
|
140
|
+
getEventFetchedAt(event) {
|
|
141
|
+
const db = this.eventStoreDb?.db;
|
|
142
|
+
if (!db) return void 0;
|
|
143
|
+
const row = db.prepare(
|
|
144
|
+
`SELECT fetched_at FROM routstr_event_cache_metadata WHERE event_id = ?`
|
|
145
|
+
).get?.(event.id);
|
|
146
|
+
return typeof row?.fetched_at === "number" ? row.fetched_at : void 0;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Check the persistent event store for fresh cached events.
|
|
150
|
+
* Returns events from SQLite if they were fetched within `maxAge`, otherwise
|
|
151
|
+
* returns empty array (caller should hit relays). Events without local fetch
|
|
152
|
+
* metadata fall back to Nostr created_at for backwards compatibility.
|
|
153
|
+
*/
|
|
154
|
+
async getCachedNostrEvents(filter, maxAge, forceRefresh = false) {
|
|
155
|
+
const eventStore = await this.ensureEventStore();
|
|
156
|
+
if (forceRefresh) return [];
|
|
157
|
+
if (!eventStore) return [];
|
|
158
|
+
const timeline = eventStore.getTimeline(filter);
|
|
159
|
+
if (timeline.length === 0) return [];
|
|
160
|
+
const cutoff = Date.now() - maxAge;
|
|
161
|
+
const freshest = Math.max(
|
|
162
|
+
...timeline.map((e) => this.getEventFetchedAt(e) ?? e.created_at * 1e3)
|
|
163
|
+
);
|
|
164
|
+
if (freshest < cutoff) return [];
|
|
165
|
+
return timeline;
|
|
166
|
+
}
|
|
60
167
|
static async init(adapter, config = {}, options = {}) {
|
|
61
168
|
const manager = new _ModelManager(adapter, config);
|
|
62
169
|
const torMode = options.torMode ?? false;
|
|
@@ -85,19 +192,31 @@ var ModelManager = class _ModelManager {
|
|
|
85
192
|
torMode
|
|
86
193
|
);
|
|
87
194
|
await this.fetchRoutstr21Models(forceRefresh);
|
|
88
|
-
await this.syncReviewedProvidersFromNostr(
|
|
195
|
+
await this.syncReviewedProvidersFromNostr(
|
|
196
|
+
filteredCachedUrls,
|
|
197
|
+
this.providerNodePubkeysByUrl,
|
|
198
|
+
forceRefresh
|
|
199
|
+
);
|
|
89
200
|
return filteredCachedUrls;
|
|
90
201
|
}
|
|
91
202
|
}
|
|
92
203
|
}
|
|
93
204
|
try {
|
|
94
|
-
const nostrProviders = await this.bootstrapFromNostr(
|
|
205
|
+
const nostrProviders = await this.bootstrapFromNostr(
|
|
206
|
+
38421,
|
|
207
|
+
torMode,
|
|
208
|
+
forceRefresh
|
|
209
|
+
);
|
|
95
210
|
if (nostrProviders.length > 0) {
|
|
96
211
|
const filtered = this.filterBaseUrlsForTor(nostrProviders, torMode);
|
|
97
212
|
this.adapter.setBaseUrlsList(filtered);
|
|
98
213
|
this.adapter.setBaseUrlsLastUpdate(Date.now());
|
|
99
214
|
await this.fetchRoutstr21Models(forceRefresh);
|
|
100
|
-
await this.syncReviewedProvidersFromNostr(
|
|
215
|
+
await this.syncReviewedProvidersFromNostr(
|
|
216
|
+
filtered,
|
|
217
|
+
this.providerNodePubkeysByUrl,
|
|
218
|
+
forceRefresh
|
|
219
|
+
);
|
|
101
220
|
return filtered;
|
|
102
221
|
}
|
|
103
222
|
} catch (e) {
|
|
@@ -106,42 +225,52 @@ var ModelManager = class _ModelManager {
|
|
|
106
225
|
return this.bootstrapFromHttp(torMode, forceRefresh);
|
|
107
226
|
}
|
|
108
227
|
/**
|
|
109
|
-
* Bootstrap providers from Nostr network (kind
|
|
228
|
+
* Bootstrap providers from Nostr network (kind 38421)
|
|
110
229
|
* @param kind The Nostr kind to fetch
|
|
111
230
|
* @param torMode Whether running in Tor context
|
|
112
231
|
* @returns Array of provider base URLs
|
|
113
232
|
*/
|
|
114
|
-
async bootstrapFromNostr(kind, torMode) {
|
|
233
|
+
async bootstrapFromNostr(kind, torMode, forceRefresh = false) {
|
|
115
234
|
const DEFAULT_RELAYS = [
|
|
116
235
|
"wss://relay.primal.net",
|
|
117
236
|
"wss://nos.lol",
|
|
118
237
|
"wss://relay.damus.io"
|
|
119
238
|
];
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
239
|
+
const cached = await this.getCachedNostrEvents(
|
|
240
|
+
{ kinds: [kind] },
|
|
241
|
+
this.cacheTTL,
|
|
242
|
+
forceRefresh
|
|
243
|
+
);
|
|
244
|
+
let sessionEvents = cached;
|
|
245
|
+
if (cached.length === 0) {
|
|
246
|
+
const pool = new RelayPool();
|
|
247
|
+
const timeoutMs = 5e3;
|
|
248
|
+
await new Promise((resolve) => {
|
|
249
|
+
pool.req(DEFAULT_RELAYS, {
|
|
250
|
+
kinds: [kind],
|
|
251
|
+
limit: 100
|
|
252
|
+
}).pipe(
|
|
253
|
+
onlyEvents(),
|
|
254
|
+
tap((event) => {
|
|
255
|
+
sessionEvents.push(event);
|
|
256
|
+
this.eventStore?.add(event);
|
|
257
|
+
this.markEventFetched(event);
|
|
258
|
+
})
|
|
259
|
+
).subscribe({
|
|
260
|
+
complete: () => {
|
|
261
|
+
resolve();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
setTimeout(() => {
|
|
134
265
|
resolve();
|
|
135
|
-
}
|
|
266
|
+
}, timeoutMs);
|
|
136
267
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
});
|
|
141
|
-
const timeline = localEventStore.getTimeline({ kinds: [kind] });
|
|
268
|
+
} else {
|
|
269
|
+
this.logger.log(`Using ${cached.length} cached kind ${kind} events from persistent store`);
|
|
270
|
+
}
|
|
142
271
|
const bases = /* @__PURE__ */ new Set();
|
|
143
272
|
this.providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
|
|
144
|
-
for (const event of
|
|
273
|
+
for (const event of sessionEvents) {
|
|
145
274
|
const eventUrls = [];
|
|
146
275
|
for (const tag of event.tags) {
|
|
147
276
|
if (tag[0] === "u" && typeof tag[1] === "string") {
|
|
@@ -249,7 +378,11 @@ var ModelManager = class _ModelManager {
|
|
|
249
378
|
this.adapter.setBaseUrlsList(list);
|
|
250
379
|
this.adapter.setBaseUrlsLastUpdate(Date.now());
|
|
251
380
|
await this.fetchRoutstr21Models(forceRefresh);
|
|
252
|
-
await this.syncReviewedProvidersFromNostr(
|
|
381
|
+
await this.syncReviewedProvidersFromNostr(
|
|
382
|
+
list,
|
|
383
|
+
this.providerNodePubkeysByUrl,
|
|
384
|
+
forceRefresh
|
|
385
|
+
);
|
|
253
386
|
}
|
|
254
387
|
return list;
|
|
255
388
|
} catch (e) {
|
|
@@ -268,7 +401,7 @@ var ModelManager = class _ModelManager {
|
|
|
268
401
|
* @param baseUrls Current provider base URLs to evaluate
|
|
269
402
|
* @returns Array of provider base URLs disabled by the review set
|
|
270
403
|
*/
|
|
271
|
-
async syncReviewedProvidersFromNostr(baseUrls = this.adapter.getBaseUrlsList(), providerNodes = this.providerNodePubkeysByUrl) {
|
|
404
|
+
async syncReviewedProvidersFromNostr(baseUrls = this.adapter.getBaseUrlsList(), providerNodes = this.providerNodePubkeysByUrl, forceRefresh = false) {
|
|
272
405
|
if (baseUrls.length === 0) return [];
|
|
273
406
|
if (!this.adapter.setDisabledProviders) {
|
|
274
407
|
this.logger.warn(
|
|
@@ -276,30 +409,43 @@ var ModelManager = class _ModelManager {
|
|
|
276
409
|
);
|
|
277
410
|
return [];
|
|
278
411
|
}
|
|
279
|
-
const LGTM_RELAYS = [
|
|
280
|
-
"wss://relay.primal.net",
|
|
281
|
-
"wss://nos.lol",
|
|
282
|
-
"wss://relay.damus.io",
|
|
283
|
-
"wss://relay.routstr.com"
|
|
284
|
-
];
|
|
285
412
|
const reviewedNodePubkeys = /* @__PURE__ */ new Set();
|
|
286
413
|
{
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
414
|
+
const cached = await this.getCachedNostrEvents(
|
|
415
|
+
{ kinds: [38425], "#t": ["lgtm"], authors: [this.routstrPubkey] },
|
|
416
|
+
this.cacheTTL,
|
|
417
|
+
forceRefresh
|
|
418
|
+
);
|
|
419
|
+
let sessionEvents = cached;
|
|
420
|
+
if (cached.length === 0) {
|
|
421
|
+
const LGTM_RELAYS = [
|
|
422
|
+
"wss://relay.primal.net",
|
|
423
|
+
"wss://nos.lol",
|
|
424
|
+
"wss://relay.damus.io",
|
|
425
|
+
"wss://relay.routstr.com"
|
|
426
|
+
];
|
|
427
|
+
const pool = new RelayPool();
|
|
428
|
+
const timeoutMs = 5e3;
|
|
429
|
+
await new Promise((resolve) => {
|
|
430
|
+
pool.req(LGTM_RELAYS, {
|
|
431
|
+
kinds: [38425],
|
|
432
|
+
"#t": ["lgtm"],
|
|
433
|
+
limit: 500,
|
|
434
|
+
authors: [this.routstrPubkey]
|
|
435
|
+
}).pipe(
|
|
436
|
+
onlyEvents(),
|
|
437
|
+
tap((event) => {
|
|
438
|
+
sessionEvents.push(event);
|
|
439
|
+
this.eventStore?.add(event);
|
|
440
|
+
this.markEventFetched(event);
|
|
441
|
+
})
|
|
442
|
+
).subscribe({ complete: () => resolve() });
|
|
443
|
+
setTimeout(() => resolve(), timeoutMs);
|
|
444
|
+
});
|
|
445
|
+
} else {
|
|
446
|
+
this.logger.log(`Using ${cached.length} cached kind 38425 events from persistent store`);
|
|
447
|
+
}
|
|
448
|
+
for (const event of sessionEvents) {
|
|
303
449
|
const hasLgtmTag = event.tags.some(
|
|
304
450
|
(tag) => tag[0] === "t" && tag[1]?.toLowerCase() === "lgtm"
|
|
305
451
|
);
|
|
@@ -408,7 +554,7 @@ var ModelManager = class _ModelManager {
|
|
|
408
554
|
if (this.isProviderDownError(error)) {
|
|
409
555
|
this.logger.warn(`Provider ${base} is down right now.`);
|
|
410
556
|
} else {
|
|
411
|
-
this.logger.warn(`
|
|
557
|
+
this.logger.warn(`Provider ${base} unreachable: ${error.message}`);
|
|
412
558
|
}
|
|
413
559
|
this.adapter.setProviderLastUpdate(base, Date.now());
|
|
414
560
|
return { success: false, base };
|
|
@@ -528,34 +674,44 @@ var ModelManager = class _ModelManager {
|
|
|
528
674
|
"wss://nos.lol",
|
|
529
675
|
"wss://relay.routstr.com"
|
|
530
676
|
];
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
677
|
+
const cached = await this.getCachedNostrEvents(
|
|
678
|
+
{ kinds: [38423], "#d": ["routstr-21-models"], authors: [this.routstrPubkey] },
|
|
679
|
+
this.cacheTTL,
|
|
680
|
+
forceRefresh
|
|
681
|
+
);
|
|
682
|
+
let sessionEvents = cached;
|
|
683
|
+
if (cached.length === 0) {
|
|
684
|
+
const pool = new RelayPool();
|
|
685
|
+
const timeoutMs = 5e3;
|
|
686
|
+
await new Promise((resolve) => {
|
|
687
|
+
pool.req(DEFAULT_RELAYS, {
|
|
688
|
+
kinds: [38423],
|
|
689
|
+
"#d": ["routstr-21-models"],
|
|
690
|
+
limit: 1,
|
|
691
|
+
authors: [this.routstrPubkey]
|
|
692
|
+
}).pipe(
|
|
693
|
+
onlyEvents(),
|
|
694
|
+
tap((event2) => {
|
|
695
|
+
sessionEvents.push(event2);
|
|
696
|
+
this.eventStore?.add(event2);
|
|
697
|
+
this.markEventFetched(event2);
|
|
698
|
+
})
|
|
699
|
+
).subscribe({
|
|
700
|
+
complete: () => {
|
|
701
|
+
resolve();
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
setTimeout(() => {
|
|
547
705
|
resolve();
|
|
548
|
-
}
|
|
706
|
+
}, timeoutMs);
|
|
549
707
|
});
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
const timeline = localEventStore.getTimeline({ kinds: [38423] });
|
|
555
|
-
if (timeline.length === 0) {
|
|
708
|
+
} else {
|
|
709
|
+
this.logger.log(`Using ${cached.length} cached kind 38423 events from persistent store`);
|
|
710
|
+
}
|
|
711
|
+
if (sessionEvents.length === 0) {
|
|
556
712
|
return cachedModels.length > 0 ? cachedModels : [];
|
|
557
713
|
}
|
|
558
|
-
const event =
|
|
714
|
+
const event = sessionEvents[0];
|
|
559
715
|
try {
|
|
560
716
|
const content = JSON.parse(event.content);
|
|
561
717
|
const models = Array.isArray(content?.models) ? content.models : [];
|