@routstr/sdk 0.3.8 → 0.3.10

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 (65) hide show
  1. package/dist/browser.d.mts +12 -0
  2. package/dist/browser.d.ts +12 -0
  3. package/dist/browser.js +6278 -0
  4. package/dist/browser.js.map +1 -0
  5. package/dist/browser.mjs +6230 -0
  6. package/dist/browser.mjs.map +1 -0
  7. package/dist/bun.d.mts +29 -0
  8. package/dist/bun.d.ts +29 -0
  9. package/dist/bun.js +6586 -0
  10. package/dist/bun.js.map +1 -0
  11. package/dist/bun.mjs +6532 -0
  12. package/dist/bun.mjs.map +1 -0
  13. package/dist/bunSqlite-BMTseLIz.d.ts +18 -0
  14. package/dist/bunSqlite-D6AreVE2.d.mts +18 -0
  15. package/dist/client/index.d.mts +63 -41
  16. package/dist/client/index.d.ts +63 -41
  17. package/dist/client/index.js +1223 -1658
  18. package/dist/client/index.js.map +1 -1
  19. package/dist/client/index.mjs +1223 -1659
  20. package/dist/client/index.mjs.map +1 -1
  21. package/dist/discovery/index.d.mts +67 -3
  22. package/dist/discovery/index.d.ts +67 -3
  23. package/dist/discovery/index.js +242 -79
  24. package/dist/discovery/index.js.map +1 -1
  25. package/dist/discovery/index.mjs +242 -79
  26. package/dist/discovery/index.mjs.map +1 -1
  27. package/dist/index.d.mts +5 -4
  28. package/dist/index.d.ts +5 -4
  29. package/dist/index.js +1975 -2004
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +1973 -2001
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/node.d.mts +22 -0
  34. package/dist/node.d.ts +22 -0
  35. package/dist/node.js +6651 -0
  36. package/dist/node.js.map +1 -0
  37. package/dist/node.mjs +6599 -0
  38. package/dist/node.mjs.map +1 -0
  39. package/dist/storage/bun.d.mts +16 -0
  40. package/dist/storage/bun.d.ts +16 -0
  41. package/dist/storage/bun.js +1801 -0
  42. package/dist/storage/bun.js.map +1 -0
  43. package/dist/storage/bun.mjs +1777 -0
  44. package/dist/storage/bun.mjs.map +1 -0
  45. package/dist/storage/index.d.mts +30 -30
  46. package/dist/storage/index.d.ts +30 -30
  47. package/dist/storage/index.js +393 -625
  48. package/dist/storage/index.js.map +1 -1
  49. package/dist/storage/index.mjs +392 -622
  50. package/dist/storage/index.mjs.map +1 -1
  51. package/dist/storage/node.d.mts +22 -0
  52. package/dist/storage/node.d.ts +22 -0
  53. package/dist/storage/node.js +1864 -0
  54. package/dist/storage/node.js.map +1 -0
  55. package/dist/storage/node.mjs +1842 -0
  56. package/dist/storage/node.mjs.map +1 -0
  57. package/dist/{store-C6dfj1cc.d.mts → store-BiuM2V9N.d.mts} +14 -0
  58. package/dist/{store-58VcEUoA.d.ts → store-C8MZlfuz.d.ts} +14 -0
  59. package/dist/wallet/index.d.mts +4 -0
  60. package/dist/wallet/index.d.ts +4 -0
  61. package/dist/wallet/index.js +11 -4
  62. package/dist/wallet/index.js.map +1 -1
  63. package/dist/wallet/index.mjs +11 -4
  64. package/dist/wallet/index.mjs.map +1 -1
  65. package/package.json +28 -2
@@ -40,7 +40,10 @@ var ModelManager = class _ModelManager {
40
40
  this.includeProviderUrls = config.includeProviderUrls || [];
41
41
  this.excludeProviderUrls = config.excludeProviderUrls || [];
42
42
  this.routstrPubkey = config.routstrPubkey || "4ad6fa2d16e2a9b576c863b4cf7404a70d4dc320c0c447d10ad6ff58993eacc8";
43
+ this.nostrRelays = config.nostrRelays;
43
44
  this.logger = (config.logger ?? consoleLogger).child("ModelManager");
45
+ this.eventStoreDbPath = config.eventStoreDbPath;
46
+ this.persistentEventDatabaseFactory = config.persistentEventDatabaseFactory;
44
47
  }
45
48
  adapter;
46
49
  cacheTTL;
@@ -48,8 +51,15 @@ var ModelManager = class _ModelManager {
48
51
  includeProviderUrls;
49
52
  excludeProviderUrls;
50
53
  routstrPubkey;
54
+ nostrRelays;
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;
62
+ persistentEventDatabaseFactory;
53
63
  /**
54
64
  * Get the list of bootstrapped provider base URLs
55
65
  * @returns Array of provider base URLs
@@ -57,6 +67,103 @@ var ModelManager = class _ModelManager {
57
67
  getBaseUrls() {
58
68
  return this.adapter.getBaseUrlsList();
59
69
  }
70
+ /**
71
+ * Lazily initialize the persistent event store.
72
+ * Returns null if no eventStoreDbPath was provided.
73
+ */
74
+ async ensureEventStore() {
75
+ if (!this.eventStoreDbPath) return null;
76
+ if (this.eventStore) return this.eventStore;
77
+ if (!this.eventStoreInitPromise) {
78
+ this.eventStoreInitPromise = (async () => {
79
+ try {
80
+ const db = await this.createPersistentEventDatabase();
81
+ this.eventStoreDb = db;
82
+ this.eventStore = new EventStore({ database: db });
83
+ this.initializeEventStoreMetadata();
84
+ this.logger.log(
85
+ `Persistent event store initialized at ${this.eventStoreDbPath}`
86
+ );
87
+ return this.eventStore;
88
+ } catch (error) {
89
+ this.eventStoreInitPromise = null;
90
+ throw new Error(
91
+ `Persistent Nostr event storage requires a runtime-specific database factory. Use @routstr/sdk/node, @routstr/sdk/bun, inject persistentEventDatabaseFactory, or omit eventStoreDbPath. (${error})`
92
+ );
93
+ }
94
+ })();
95
+ }
96
+ return this.eventStoreInitPromise;
97
+ }
98
+ /**
99
+ * Get the persistent event store, initializing it if configured.
100
+ * Returns null if no eventStoreDbPath was provided.
101
+ */
102
+ async getEventStore() {
103
+ return this.ensureEventStore();
104
+ }
105
+ async createPersistentEventDatabase() {
106
+ if (!this.eventStoreDbPath) {
107
+ throw new Error("eventStoreDbPath is required");
108
+ }
109
+ if (!this.persistentEventDatabaseFactory) {
110
+ throw new Error(
111
+ "persistentEventDatabaseFactory is required. Import ModelManager from @routstr/sdk/node or @routstr/sdk/bun for SQLite-backed persistent event storage."
112
+ );
113
+ }
114
+ return this.persistentEventDatabaseFactory(this.eventStoreDbPath);
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(filteredCachedUrls);
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(38421, torMode);
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(filtered);
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,59 @@ var ModelManager = class _ModelManager {
106
225
  return this.bootstrapFromHttp(torMode, forceRefresh);
107
226
  }
108
227
  /**
109
- * Bootstrap providers from Nostr network (kind 30421)
228
+ * Resolve Nostr relay URLs for a given use case.
229
+ * Returns user-configured relays if set, otherwise the provided defaults.
230
+ */
231
+ getNostrRelays(defaults) {
232
+ return this.nostrRelays && this.nostrRelays.length > 0 ? this.nostrRelays : defaults;
233
+ }
234
+ /**
235
+ * Bootstrap providers from Nostr network (kind 38421)
110
236
  * @param kind The Nostr kind to fetch
111
237
  * @param torMode Whether running in Tor context
112
238
  * @returns Array of provider base URLs
113
239
  */
114
- async bootstrapFromNostr(kind, torMode) {
115
- const DEFAULT_RELAYS = [
240
+ async bootstrapFromNostr(kind, torMode, forceRefresh = false) {
241
+ const relays = this.getNostrRelays([
116
242
  "wss://relay.primal.net",
117
243
  "wss://nos.lol",
118
244
  "wss://relay.damus.io"
119
- ];
120
- const pool = new RelayPool();
121
- const localEventStore = new EventStore();
122
- const timeoutMs = 5e3;
123
- await new Promise((resolve) => {
124
- pool.req(DEFAULT_RELAYS, {
125
- kinds: [kind],
126
- limit: 100
127
- }).pipe(
128
- onlyEvents(),
129
- tap((event) => {
130
- localEventStore.add(event);
131
- })
132
- ).subscribe({
133
- complete: () => {
245
+ ]);
246
+ const cached = await this.getCachedNostrEvents(
247
+ { kinds: [kind] },
248
+ this.cacheTTL,
249
+ forceRefresh
250
+ );
251
+ let sessionEvents = cached;
252
+ if (cached.length === 0) {
253
+ const pool = new RelayPool();
254
+ const timeoutMs = 5e3;
255
+ await new Promise((resolve) => {
256
+ pool.req(relays, {
257
+ kinds: [kind],
258
+ limit: 100
259
+ }).pipe(
260
+ onlyEvents(),
261
+ tap((event) => {
262
+ sessionEvents.push(event);
263
+ this.eventStore?.add(event);
264
+ this.markEventFetched(event);
265
+ })
266
+ ).subscribe({
267
+ complete: () => {
268
+ resolve();
269
+ }
270
+ });
271
+ setTimeout(() => {
134
272
  resolve();
135
- }
273
+ }, timeoutMs);
136
274
  });
137
- setTimeout(() => {
138
- resolve();
139
- }, timeoutMs);
140
- });
141
- const timeline = localEventStore.getTimeline({ kinds: [kind] });
275
+ } else {
276
+ this.logger.log(`Using ${cached.length} cached kind ${kind} events from persistent store`);
277
+ }
142
278
  const bases = /* @__PURE__ */ new Set();
143
279
  this.providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
144
- for (const event of timeline) {
280
+ for (const event of sessionEvents) {
145
281
  const eventUrls = [];
146
282
  for (const tag of event.tags) {
147
283
  if (tag[0] === "u" && typeof tag[1] === "string") {
@@ -249,7 +385,11 @@ var ModelManager = class _ModelManager {
249
385
  this.adapter.setBaseUrlsList(list);
250
386
  this.adapter.setBaseUrlsLastUpdate(Date.now());
251
387
  await this.fetchRoutstr21Models(forceRefresh);
252
- await this.syncReviewedProvidersFromNostr(list);
388
+ await this.syncReviewedProvidersFromNostr(
389
+ list,
390
+ this.providerNodePubkeysByUrl,
391
+ forceRefresh
392
+ );
253
393
  }
254
394
  return list;
255
395
  } catch (e) {
@@ -268,7 +408,7 @@ var ModelManager = class _ModelManager {
268
408
  * @param baseUrls Current provider base URLs to evaluate
269
409
  * @returns Array of provider base URLs disabled by the review set
270
410
  */
271
- async syncReviewedProvidersFromNostr(baseUrls = this.adapter.getBaseUrlsList(), providerNodes = this.providerNodePubkeysByUrl) {
411
+ async syncReviewedProvidersFromNostr(baseUrls = this.adapter.getBaseUrlsList(), providerNodes = this.providerNodePubkeysByUrl, forceRefresh = false) {
272
412
  if (baseUrls.length === 0) return [];
273
413
  if (!this.adapter.setDisabledProviders) {
274
414
  this.logger.warn(
@@ -276,30 +416,43 @@ var ModelManager = class _ModelManager {
276
416
  );
277
417
  return [];
278
418
  }
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
419
  const reviewedNodePubkeys = /* @__PURE__ */ new Set();
286
420
  {
287
- const pool = new RelayPool();
288
- const store = new EventStore();
289
- const timeoutMs = 5e3;
290
- await new Promise((resolve) => {
291
- pool.req(LGTM_RELAYS, {
292
- kinds: [38425],
293
- "#t": ["lgtm"],
294
- limit: 500,
295
- authors: [this.routstrPubkey]
296
- }).pipe(
297
- onlyEvents(),
298
- tap((event) => store.add(event))
299
- ).subscribe({ complete: () => resolve() });
300
- setTimeout(() => resolve(), timeoutMs);
301
- });
302
- for (const event of store.getTimeline({ kinds: [38425] })) {
421
+ const cached = await this.getCachedNostrEvents(
422
+ { kinds: [38425], "#t": ["lgtm"], authors: [this.routstrPubkey] },
423
+ this.cacheTTL,
424
+ forceRefresh
425
+ );
426
+ let sessionEvents = cached;
427
+ if (cached.length === 0) {
428
+ const lgtmRelays = this.getNostrRelays([
429
+ "wss://relay.primal.net",
430
+ "wss://nos.lol",
431
+ "wss://relay.damus.io",
432
+ "wss://relay.routstr.com"
433
+ ]);
434
+ const pool = new RelayPool();
435
+ const timeoutMs = 5e3;
436
+ await new Promise((resolve) => {
437
+ pool.req(lgtmRelays, {
438
+ kinds: [38425],
439
+ "#t": ["lgtm"],
440
+ limit: 500,
441
+ authors: [this.routstrPubkey]
442
+ }).pipe(
443
+ onlyEvents(),
444
+ tap((event) => {
445
+ sessionEvents.push(event);
446
+ this.eventStore?.add(event);
447
+ this.markEventFetched(event);
448
+ })
449
+ ).subscribe({ complete: () => resolve() });
450
+ setTimeout(() => resolve(), timeoutMs);
451
+ });
452
+ } else {
453
+ this.logger.log(`Using ${cached.length} cached kind 38425 events from persistent store`);
454
+ }
455
+ for (const event of sessionEvents) {
303
456
  const hasLgtmTag = event.tags.some(
304
457
  (tag) => tag[0] === "t" && tag[1]?.toLowerCase() === "lgtm"
305
458
  );
@@ -408,7 +561,7 @@ var ModelManager = class _ModelManager {
408
561
  if (this.isProviderDownError(error)) {
409
562
  this.logger.warn(`Provider ${base} is down right now.`);
410
563
  } else {
411
- this.logger.warn(`Failed to fetch models from ${base}:`, error);
564
+ this.logger.warn(`Provider ${base} unreachable: ${error.message}`);
412
565
  }
413
566
  this.adapter.setProviderLastUpdate(base, Date.now());
414
567
  return { success: false, base };
@@ -523,39 +676,49 @@ var ModelManager = class _ModelManager {
523
676
  return cachedModels;
524
677
  }
525
678
  }
526
- const DEFAULT_RELAYS = [
679
+ const relays = this.getNostrRelays([
527
680
  "wss://relay.damus.io",
528
681
  "wss://nos.lol",
529
682
  "wss://relay.routstr.com"
530
- ];
531
- const pool = new RelayPool();
532
- const localEventStore = new EventStore();
533
- const timeoutMs = 5e3;
534
- await new Promise((resolve) => {
535
- pool.req(DEFAULT_RELAYS, {
536
- kinds: [38423],
537
- "#d": ["routstr-21-models"],
538
- limit: 1,
539
- authors: [this.routstrPubkey]
540
- }).pipe(
541
- onlyEvents(),
542
- tap((event2) => {
543
- localEventStore.add(event2);
544
- })
545
- ).subscribe({
546
- complete: () => {
683
+ ]);
684
+ const cached = await this.getCachedNostrEvents(
685
+ { kinds: [38423], "#d": ["routstr-21-models"], authors: [this.routstrPubkey] },
686
+ this.cacheTTL,
687
+ forceRefresh
688
+ );
689
+ let sessionEvents = cached;
690
+ if (cached.length === 0) {
691
+ const pool = new RelayPool();
692
+ const timeoutMs = 5e3;
693
+ await new Promise((resolve) => {
694
+ pool.req(relays, {
695
+ kinds: [38423],
696
+ "#d": ["routstr-21-models"],
697
+ limit: 1,
698
+ authors: [this.routstrPubkey]
699
+ }).pipe(
700
+ onlyEvents(),
701
+ tap((event2) => {
702
+ sessionEvents.push(event2);
703
+ this.eventStore?.add(event2);
704
+ this.markEventFetched(event2);
705
+ })
706
+ ).subscribe({
707
+ complete: () => {
708
+ resolve();
709
+ }
710
+ });
711
+ setTimeout(() => {
547
712
  resolve();
548
- }
713
+ }, timeoutMs);
549
714
  });
550
- setTimeout(() => {
551
- resolve();
552
- }, timeoutMs);
553
- });
554
- const timeline = localEventStore.getTimeline({ kinds: [38423] });
555
- if (timeline.length === 0) {
715
+ } else {
716
+ this.logger.log(`Using ${cached.length} cached kind 38423 events from persistent store`);
717
+ }
718
+ if (sessionEvents.length === 0) {
556
719
  return cachedModels.length > 0 ? cachedModels : [];
557
720
  }
558
- const event = timeline[0];
721
+ const event = sessionEvents[0];
559
722
  try {
560
723
  const content = JSON.parse(event.content);
561
724
  const models = Array.isArray(content?.models) ? content.models : [];