@elisym/sdk 0.3.2 → 0.3.3

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/index.d.cts CHANGED
@@ -35,7 +35,22 @@ declare class NostrPool {
35
35
  private pool;
36
36
  private relays;
37
37
  private activeSubscriptions;
38
+ private resetListeners;
38
39
  constructor(relays?: string[]);
40
+ /**
41
+ * Register a callback to run after `reset()` completes (new SimplePool in place).
42
+ * Services that cache pool-derived state (e.g. ping results) must clear it here,
43
+ * otherwise stale values survive the reconnect. Returns an unsubscribe function.
44
+ *
45
+ * Contract:
46
+ * - Listeners are invoked **synchronously** at the end of `reset()`, in
47
+ * registration order. Do not rely on async work inside a listener having
48
+ * completed before `reset()` returns.
49
+ * - Listener exceptions are caught and swallowed so that one faulty listener
50
+ * cannot prevent the others from running (or abort the reset itself).
51
+ * If a listener needs to surface errors, it must do so out-of-band.
52
+ */
53
+ onReset(listener: () => void): () => void;
39
54
  /** Query relays synchronously. Returns `[]` on timeout (no error thrown). */
40
55
  querySync(filter: Filter): Promise<Event[]>;
41
56
  queryBatched(filter: Omit<Filter, 'authors'>, keys: string[], batchSize?: number, maxConcurrency?: number): Promise<Event[]>;
@@ -189,6 +204,20 @@ declare class MediaService {
189
204
  * this instance - recreating the service generates a new keypair.
190
205
  *
191
206
  * Requires `globalThis.crypto` (Node 20+, Bun, browsers).
207
+ *
208
+ * Lifetime / cleanup:
209
+ * - The constructor registers a listener on `pool.onReset()` and never
210
+ * unsubscribes. This is intentional: PingService is expected to share its
211
+ * lifetime with the NostrPool it is bound to (both live for the lifetime
212
+ * of an ElisymClient). If you ever construct a PingService that outlives
213
+ * its pool - or vice versa - add an explicit `dispose()` that calls the
214
+ * unsubscribe function returned by `pool.onReset()` to avoid leaking a
215
+ * reference to this instance through the pool's listener set.
216
+ * - `clearCache()` drops cached online results but does NOT cancel in-flight
217
+ * pings in `pendingPings`. An in-flight ping started before a pool reset
218
+ * may return `online: false` even if the new pool is healthy; the next
219
+ * ping attempt will go through the fresh subscription and resolve
220
+ * correctly.
192
221
  */
193
222
  declare class PingService {
194
223
  private pool;
@@ -197,6 +226,9 @@ declare class PingService {
197
226
  private pingCache;
198
227
  private pendingPings;
199
228
  constructor(pool: NostrPool);
229
+ /** Drop cached online results. In-flight pings are left alone - they'll
230
+ * resolve via their own timeouts and remove themselves from `pendingPings`. */
231
+ clearCache(): void;
200
232
  /**
201
233
  * Ping an agent via ephemeral Nostr events (kind 20200/20201).
202
234
  * Uses a persistent session identity to avoid relay rate-limiting.
package/dist/index.d.ts CHANGED
@@ -35,7 +35,22 @@ declare class NostrPool {
35
35
  private pool;
36
36
  private relays;
37
37
  private activeSubscriptions;
38
+ private resetListeners;
38
39
  constructor(relays?: string[]);
40
+ /**
41
+ * Register a callback to run after `reset()` completes (new SimplePool in place).
42
+ * Services that cache pool-derived state (e.g. ping results) must clear it here,
43
+ * otherwise stale values survive the reconnect. Returns an unsubscribe function.
44
+ *
45
+ * Contract:
46
+ * - Listeners are invoked **synchronously** at the end of `reset()`, in
47
+ * registration order. Do not rely on async work inside a listener having
48
+ * completed before `reset()` returns.
49
+ * - Listener exceptions are caught and swallowed so that one faulty listener
50
+ * cannot prevent the others from running (or abort the reset itself).
51
+ * If a listener needs to surface errors, it must do so out-of-band.
52
+ */
53
+ onReset(listener: () => void): () => void;
39
54
  /** Query relays synchronously. Returns `[]` on timeout (no error thrown). */
40
55
  querySync(filter: Filter): Promise<Event[]>;
41
56
  queryBatched(filter: Omit<Filter, 'authors'>, keys: string[], batchSize?: number, maxConcurrency?: number): Promise<Event[]>;
@@ -189,6 +204,20 @@ declare class MediaService {
189
204
  * this instance - recreating the service generates a new keypair.
190
205
  *
191
206
  * Requires `globalThis.crypto` (Node 20+, Bun, browsers).
207
+ *
208
+ * Lifetime / cleanup:
209
+ * - The constructor registers a listener on `pool.onReset()` and never
210
+ * unsubscribes. This is intentional: PingService is expected to share its
211
+ * lifetime with the NostrPool it is bound to (both live for the lifetime
212
+ * of an ElisymClient). If you ever construct a PingService that outlives
213
+ * its pool - or vice versa - add an explicit `dispose()` that calls the
214
+ * unsubscribe function returned by `pool.onReset()` to avoid leaking a
215
+ * reference to this instance through the pool's listener set.
216
+ * - `clearCache()` drops cached online results but does NOT cancel in-flight
217
+ * pings in `pendingPings`. An in-flight ping started before a pool reset
218
+ * may return `online: false` even if the new pool is healthy; the next
219
+ * ping attempt will go through the fresh subscription and resolve
220
+ * correctly.
192
221
  */
193
222
  declare class PingService {
194
223
  private pool;
@@ -197,6 +226,9 @@ declare class PingService {
197
226
  private pingCache;
198
227
  private pendingPings;
199
228
  constructor(pool: NostrPool);
229
+ /** Drop cached online results. In-flight pings are left alone - they'll
230
+ * resolve via their own timeouts and remove themselves from `pendingPings`. */
231
+ clearCache(): void;
200
232
  /**
201
233
  * Ping an agent via ephemeral Nostr events (kind 20200/20201).
202
234
  * Uses a persistent session identity to avoid relay rate-limiting.
package/dist/index.js CHANGED
@@ -1571,12 +1571,18 @@ var PingService = class _PingService {
1571
1571
  constructor(pool) {
1572
1572
  this.pool = pool;
1573
1573
  this.sessionIdentity = ElisymIdentity.generate();
1574
+ pool.onReset(() => this.clearCache());
1574
1575
  }
1575
1576
  static PING_CACHE_MAX = 1e3;
1576
1577
  sessionIdentity;
1577
1578
  pingCache = /* @__PURE__ */ new Map();
1578
1579
  // pubkey - timestamp of last online result
1579
1580
  pendingPings = /* @__PURE__ */ new Map();
1581
+ /** Drop cached online results. In-flight pings are left alone - they'll
1582
+ * resolve via their own timeouts and remove themselves from `pendingPings`. */
1583
+ clearCache() {
1584
+ this.pingCache.clear();
1585
+ }
1580
1586
  /**
1581
1587
  * Ping an agent via ephemeral Nostr events (kind 20200/20201).
1582
1588
  * Uses a persistent session identity to avoid relay rate-limiting.
@@ -1775,10 +1781,28 @@ var NostrPool = class {
1775
1781
  pool;
1776
1782
  relays;
1777
1783
  activeSubscriptions = /* @__PURE__ */ new Set();
1784
+ resetListeners = /* @__PURE__ */ new Set();
1778
1785
  constructor(relays = RELAYS) {
1779
1786
  this.pool = new SimplePool();
1780
1787
  this.relays = relays;
1781
1788
  }
1789
+ /**
1790
+ * Register a callback to run after `reset()` completes (new SimplePool in place).
1791
+ * Services that cache pool-derived state (e.g. ping results) must clear it here,
1792
+ * otherwise stale values survive the reconnect. Returns an unsubscribe function.
1793
+ *
1794
+ * Contract:
1795
+ * - Listeners are invoked **synchronously** at the end of `reset()`, in
1796
+ * registration order. Do not rely on async work inside a listener having
1797
+ * completed before `reset()` returns.
1798
+ * - Listener exceptions are caught and swallowed so that one faulty listener
1799
+ * cannot prevent the others from running (or abort the reset itself).
1800
+ * If a listener needs to surface errors, it must do so out-of-band.
1801
+ */
1802
+ onReset(listener) {
1803
+ this.resetListeners.add(listener);
1804
+ return () => this.resetListeners.delete(listener);
1805
+ }
1782
1806
  /** Query relays synchronously. Returns `[]` on timeout (no error thrown). */
1783
1807
  async querySync(filter) {
1784
1808
  let timer;
@@ -1786,13 +1810,12 @@ var NostrPool = class {
1786
1810
  query.catch(() => {
1787
1811
  });
1788
1812
  try {
1789
- const result = await Promise.race([
1813
+ return await Promise.race([
1790
1814
  query,
1791
1815
  new Promise((resolve) => {
1792
1816
  timer = setTimeout(() => resolve([]), DEFAULTS.QUERY_TIMEOUT_MS);
1793
1817
  })
1794
1818
  ]);
1795
- return result;
1796
1819
  } finally {
1797
1820
  clearTimeout(timer);
1798
1821
  }
@@ -1974,6 +1997,12 @@ var NostrPool = class {
1974
1997
  } catch {
1975
1998
  }
1976
1999
  this.pool = new SimplePool();
2000
+ for (const listener of this.resetListeners) {
2001
+ try {
2002
+ listener();
2003
+ } catch {
2004
+ }
2005
+ }
1977
2006
  }
1978
2007
  /**
1979
2008
  * Lightweight connectivity probe. Returns true if at least one relay responds.