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