@massalabs/gossip-sdk 0.0.2-dev.20260211110128 → 0.0.2-dev.20260211172752

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 CHANGED
@@ -47,9 +47,8 @@ await gossipSdk.openSession({
47
47
  mnemonic: 'word1 word2 word3 ... word12',
48
48
  // For existing session:
49
49
  encryptedSession: savedBlob,
50
- encryptionKey: savedKey,
50
+ encryptionKey,
51
51
  // For persistence:
52
- persistEncryptionKey: encryptionKey,
53
52
  onPersist: async (blob, key) => {
54
53
  await saveToStorage(blob, key);
55
54
  },
@@ -113,7 +112,7 @@ await gossipSdk.openSession({
113
112
  // With persistence (saves session on changes)
114
113
  await gossipSdk.openSession({
115
114
  mnemonic: 'word1 word2 word3 ... word12',
116
- persistEncryptionKey: encryptionKey,
115
+ encryptionKey,
117
116
  onPersist: async (blob, key) => {
118
117
  await db.userProfile.update(userId, { session: blob });
119
118
  },
@@ -387,25 +386,16 @@ const decoded = utils.decodeUserId(encodedString);
387
386
 
388
387
  ## Session Persistence
389
388
 
390
- For restoring sessions across app restarts:
389
+ For restoring sessions across app restarts, pass `encryptionKey` and `onPersist` when opening the session:
391
390
 
392
391
  ```typescript
393
- // Option 1: Provide persistence config in openSession
394
392
  await gossipSdk.openSession({
395
393
  mnemonic,
396
- persistEncryptionKey: key,
394
+ encryptionKey,
397
395
  onPersist: async (blob, key) => {
398
- // Save blob to your storage
399
396
  await db.userProfile.update(userId, { session: blob });
400
397
  },
401
398
  });
402
-
403
- // Option 2: Configure persistence after account creation
404
- await gossipSdk.openSession({ mnemonic });
405
- // ... create account, get encryption key ...
406
- gossipSdk.configurePersistence(encryptionKey, async (blob, key) => {
407
- await db.userProfile.update(userId, { session: blob });
408
- });
409
399
  ```
410
400
 
411
401
  ## Auto-Renewal Behavior
@@ -23,7 +23,7 @@ export declare const defaultMessageProtocol: MessageProtocolType;
23
23
  *
24
24
  * @example
25
25
  * ```typescript
26
- * import { setProtocolBaseUrl } from 'gossip-sdk';
26
+ * import { setProtocolBaseUrl } from '@massalabs/gossip-sdk';
27
27
  *
28
28
  * // Set custom API endpoint
29
29
  * setProtocolBaseUrl('https://my-server.com/api');
@@ -66,7 +66,7 @@ export const defaultMessageProtocol = MessageProtocolType.REST;
66
66
  *
67
67
  * @example
68
68
  * ```typescript
69
- * import { setProtocolBaseUrl } from 'gossip-sdk';
69
+ * import { setProtocolBaseUrl } from '@massalabs/gossip-sdk';
70
70
  *
71
71
  * // Set custom API endpoint
72
72
  * setProtocolBaseUrl('https://my-server.com/api');
@@ -43,6 +43,8 @@ export interface MessagesConfig {
43
43
  * resulting in message being re-sent on restart.
44
44
  */
45
45
  deduplicationWindowMs: number;
46
+ /** Delay before retrying a failed message send in ms (default: 5000 = 5 seconds) */
47
+ retryDelayMs: number;
46
48
  }
47
49
  /**
48
50
  * Announcement configuration
@@ -52,6 +54,8 @@ export interface AnnouncementsConfig {
52
54
  fetchLimit: number;
53
55
  /** Time before marking failed announcements as broken in ms (default: 3600000 = 1 hour) */
54
56
  brokenThresholdMs: number;
57
+ /** Delay before retrying a failed announcement send in ms (default: 15000 = 15 seconds) */
58
+ retryDelayMs: number;
55
59
  }
56
60
  /**
57
61
  * Complete SDK configuration
@@ -16,16 +16,18 @@ export const defaultSdkConfig = {
16
16
  enabled: false,
17
17
  messagesIntervalMs: 5000,
18
18
  announcementsIntervalMs: 10000,
19
- sessionRefreshIntervalMs: 30000,
19
+ sessionRefreshIntervalMs: 10000, //30000,
20
20
  },
21
21
  messages: {
22
22
  fetchDelayMs: 100,
23
23
  maxFetchIterations: 30,
24
24
  deduplicationWindowMs: 30000, // 30 seconds
25
+ retryDelayMs: 5000, // 5 seconds
25
26
  },
26
27
  announcements: {
27
28
  fetchLimit: 500,
28
29
  brokenThresholdMs: 60 * 60 * 1000, // 1 hour
30
+ retryDelayMs: 15000, // 15 seconds
29
31
  },
30
32
  };
31
33
  /**
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @example
7
7
  * ```typescript
8
- * import { getContacts, addContact, deleteContact } from 'gossip-sdk';
8
+ * import { getContacts, addContact, deleteContact } from '@massalabs/gossip-sdk';
9
9
  *
10
10
  * // Get all contacts
11
11
  * const contacts = await getContacts(userId);
package/dist/contacts.js CHANGED
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @example
7
7
  * ```typescript
8
- * import { getContacts, addContact, deleteContact } from 'gossip-sdk';
8
+ * import { getContacts, addContact, deleteContact } from '@massalabs/gossip-sdk';
9
9
  *
10
10
  * // Get all contacts
11
11
  * const contacts = await getContacts(userId);
@@ -4,31 +4,40 @@
4
4
  * Type-safe event emitter for SDK events.
5
5
  */
6
6
  import type { Message, Discussion, Contact } from '../db';
7
- export type SdkEventType = 'message' | 'messageSent' | 'messageFailed' | 'discussionRequest' | 'discussionStatusChanged' | 'sessionBroken' | 'sessionRenewed' | 'error';
7
+ export declare enum SdkEventType {
8
+ MESSAGE_RECEIVED = "messageReceived",
9
+ MESSAGE_SENT = "messageSent",
10
+ MESSAGE_FAILED = "messageFailed",
11
+ SESSION_REQUESTED = "sessionRequested",
12
+ SESSION_CREATED = "sessionCreated",
13
+ SESSION_RENEWED = "sessionRenewed",
14
+ SESSION_ACCEPTED = "sessionAccepted",
15
+ ERROR = "error"
16
+ }
8
17
  export interface SdkEventHandlers {
9
- message: (message: Message) => void;
10
- messageSent: (message: Message) => void;
11
- messageFailed: (message: Message, error: Error) => void;
12
- discussionRequest: (discussion: Discussion, contact: Contact) => void;
13
- discussionStatusChanged: (discussion: Discussion) => void;
14
- sessionBroken: (discussion: Discussion) => void;
15
- sessionRenewed: (discussion: Discussion) => void;
16
- error: (error: Error, context: string) => void;
18
+ [SdkEventType.MESSAGE_RECEIVED]: (message: Message) => void;
19
+ [SdkEventType.MESSAGE_SENT]: (message: Message) => void;
20
+ [SdkEventType.MESSAGE_FAILED]: (message: Message, error: Error) => void;
21
+ [SdkEventType.SESSION_REQUESTED]: (discussion: Discussion, contact: Contact) => void;
22
+ [SdkEventType.SESSION_CREATED]: (discussion: Discussion) => void;
23
+ [SdkEventType.SESSION_RENEWED]: (discussion: Discussion) => void;
24
+ [SdkEventType.SESSION_ACCEPTED]: (contactUserId: string) => void;
25
+ [SdkEventType.ERROR]: (error: Error, context: string) => void;
17
26
  }
18
27
  export declare class SdkEventEmitter {
19
28
  private handlers;
20
29
  /**
21
30
  * Register an event handler
22
31
  */
23
- on<K extends SdkEventType>(event: K, handler: SdkEventHandlers[K]): void;
32
+ on<K extends keyof SdkEventHandlers>(event: K, handler: SdkEventHandlers[K]): void;
24
33
  /**
25
34
  * Remove an event handler
26
35
  */
27
- off<K extends SdkEventType>(event: K, handler: SdkEventHandlers[K]): void;
36
+ off<K extends keyof SdkEventHandlers>(event: K, handler: SdkEventHandlers[K]): void;
28
37
  /**
29
38
  * Emit an event to all registered handlers
30
39
  */
31
- emit<K extends SdkEventType>(event: K, ...args: Parameters<SdkEventHandlers[K]>): void;
40
+ emit<K extends keyof SdkEventHandlers>(event: K, ...args: Parameters<SdkEventHandlers[K]>): void;
32
41
  /**
33
42
  * Remove all handlers for all events
34
43
  */
@@ -4,6 +4,20 @@
4
4
  * Type-safe event emitter for SDK events.
5
5
  */
6
6
  // ─────────────────────────────────────────────────────────────────────────────
7
+ // Event Types
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ export var SdkEventType;
10
+ (function (SdkEventType) {
11
+ SdkEventType["MESSAGE_RECEIVED"] = "messageReceived";
12
+ SdkEventType["MESSAGE_SENT"] = "messageSent";
13
+ SdkEventType["MESSAGE_FAILED"] = "messageFailed";
14
+ SdkEventType["SESSION_REQUESTED"] = "sessionRequested";
15
+ SdkEventType["SESSION_CREATED"] = "sessionCreated";
16
+ SdkEventType["SESSION_RENEWED"] = "sessionRenewed";
17
+ SdkEventType["SESSION_ACCEPTED"] = "sessionAccepted";
18
+ SdkEventType["ERROR"] = "error";
19
+ })(SdkEventType || (SdkEventType = {}));
20
+ // ─────────────────────────────────────────────────────────────────────────────
7
21
  // Event Emitter Class
8
22
  // ─────────────────────────────────────────────────────────────────────────────
9
23
  export class SdkEventEmitter {
@@ -13,14 +27,14 @@ export class SdkEventEmitter {
13
27
  configurable: true,
14
28
  writable: true,
15
29
  value: {
16
- message: new Set(),
17
- messageSent: new Set(),
18
- messageFailed: new Set(),
19
- discussionRequest: new Set(),
20
- discussionStatusChanged: new Set(),
21
- sessionBroken: new Set(),
22
- sessionRenewed: new Set(),
23
- error: new Set(),
30
+ [SdkEventType.MESSAGE_RECEIVED]: new Set(),
31
+ [SdkEventType.MESSAGE_SENT]: new Set(),
32
+ [SdkEventType.MESSAGE_FAILED]: new Set(),
33
+ [SdkEventType.SESSION_REQUESTED]: new Set(),
34
+ [SdkEventType.SESSION_CREATED]: new Set(),
35
+ [SdkEventType.SESSION_RENEWED]: new Set(),
36
+ [SdkEventType.SESSION_ACCEPTED]: new Set(),
37
+ [SdkEventType.ERROR]: new Set(),
24
38
  }
25
39
  });
26
40
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import type { SdkConfig } from '../config/sdk';
7
7
  import type { Discussion } from '../db';
8
+ import { SdkEventEmitter } from './SdkEventEmitter';
8
9
  export interface PollingCallbacks {
9
10
  /** Fetch messages from protocol */
10
11
  fetchMessages: () => Promise<void>;
@@ -14,16 +15,15 @@ export interface PollingCallbacks {
14
15
  handleSessionRefresh: (discussions: Discussion[]) => Promise<void>;
15
16
  /** Get active discussions for session refresh */
16
17
  getActiveDiscussions: () => Promise<Discussion[]>;
17
- /** Called when a polling error occurs */
18
- onError: (error: Error, context: string) => void;
19
18
  }
20
19
  export declare class SdkPolling {
21
20
  private timers;
22
21
  private callbacks;
22
+ private eventEmitter;
23
23
  /**
24
24
  * Start polling with the given configuration and callbacks.
25
25
  */
26
- start(config: SdkConfig, callbacks: PollingCallbacks): void;
26
+ start(config: SdkConfig, callbacks: PollingCallbacks, eventEmitter: SdkEventEmitter): void;
27
27
  /**
28
28
  * Stop all polling timers.
29
29
  */
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Manages polling timers for messages, announcements, and session refresh.
5
5
  */
6
+ import { SdkEventType } from './SdkEventEmitter';
6
7
  // ─────────────────────────────────────────────────────────────────────────────
7
8
  // Polling Manager Class
8
9
  // ─────────────────────────────────────────────────────────────────────────────
@@ -24,14 +25,21 @@ export class SdkPolling {
24
25
  writable: true,
25
26
  value: null
26
27
  });
28
+ Object.defineProperty(this, "eventEmitter", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: null
33
+ });
27
34
  }
28
35
  /**
29
36
  * Start polling with the given configuration and callbacks.
30
37
  */
31
- start(config, callbacks) {
38
+ start(config, callbacks, eventEmitter) {
32
39
  // Stop any existing timers first
33
40
  this.stop();
34
41
  this.callbacks = callbacks;
42
+ this.eventEmitter = eventEmitter;
35
43
  console.log('[SdkPolling] Starting polling', {
36
44
  messagesIntervalMs: config.polling.messagesIntervalMs,
37
45
  announcementsIntervalMs: config.polling.announcementsIntervalMs,
@@ -43,8 +51,8 @@ export class SdkPolling {
43
51
  await this.callbacks?.fetchMessages();
44
52
  }
45
53
  catch (error) {
46
- console.error('[SdkPolling] Message polling error:', error);
47
- this.callbacks?.onError(error instanceof Error ? error : new Error(String(error)), 'message_polling');
54
+ const err = error instanceof Error ? error : new Error(String(error));
55
+ this.eventEmitter?.emit(SdkEventType.ERROR, err, 'message_polling');
48
56
  }
49
57
  }, config.polling.messagesIntervalMs);
50
58
  // Start announcement polling
@@ -53,8 +61,8 @@ export class SdkPolling {
53
61
  await this.callbacks?.fetchAnnouncements();
54
62
  }
55
63
  catch (error) {
56
- console.error('[SdkPolling] Announcement polling error:', error);
57
- this.callbacks?.onError(error instanceof Error ? error : new Error(String(error)), 'announcement_polling');
64
+ const err = error instanceof Error ? error : new Error(String(error));
65
+ this.eventEmitter?.emit(SdkEventType.ERROR, err, 'announcement_polling');
58
66
  }
59
67
  }, config.polling.announcementsIntervalMs);
60
68
  // Start session refresh polling
@@ -66,8 +74,8 @@ export class SdkPolling {
66
74
  }
67
75
  }
68
76
  catch (error) {
69
- console.error('[SdkPolling] Session refresh polling error:', error);
70
- this.callbacks?.onError(error instanceof Error ? error : new Error(String(error)), 'session_refresh_polling');
77
+ const err = error instanceof Error ? error : new Error(String(error));
78
+ this.eventEmitter?.emit(SdkEventType.ERROR, err, 'session_update');
71
79
  }
72
80
  }, config.polling.sessionRefreshIntervalMs);
73
81
  }
@@ -88,6 +96,7 @@ export class SdkPolling {
88
96
  this.timers.sessionRefresh = null;
89
97
  }
90
98
  this.callbacks = null;
99
+ this.eventEmitter = null;
91
100
  }
92
101
  /**
93
102
  * Check if polling is currently running.
package/dist/db.d.ts CHANGED
@@ -23,6 +23,8 @@ export interface Message {
23
23
  contactUserId: string;
24
24
  content: string;
25
25
  serializedContent?: Uint8Array;
26
+ encryptedMessage?: Uint8Array;
27
+ whenToSend?: Date;
26
28
  type: MessageType;
27
29
  direction: MessageDirection;
28
30
  status: MessageStatus;
@@ -37,7 +39,6 @@ export interface Message {
37
39
  originalContent?: string;
38
40
  originalSeeker: Uint8Array;
39
41
  };
40
- encryptedMessage?: Uint8Array;
41
42
  }
42
43
  export interface UserProfile {
43
44
  userId: string;
@@ -65,13 +66,9 @@ export interface UserProfile {
65
66
  lastPublicKeyPush?: Date;
66
67
  lastBulletinCounter?: string;
67
68
  }
68
- export declare enum DiscussionStatus {
69
- PENDING = "pending",
70
- ACTIVE = "active",
71
- CLOSED = "closed",// closed by the user
72
- BROKEN = "broken",// The session is killed. Need to be reinitiated
73
- SEND_FAILED = "sendFailed",// The discussion was initiated by the session manager but could not be broadcasted on network
74
- RECONNECTING = "reconnecting"
69
+ export declare enum DiscussionDirection {
70
+ RECEIVED = "received",
71
+ INITIATED = "initiated"
75
72
  }
76
73
  export declare enum MessageDirection {
77
74
  INCOMING = "incoming",
@@ -79,35 +76,35 @@ export declare enum MessageDirection {
79
76
  }
80
77
  export declare enum MessageStatus {
81
78
  WAITING_SESSION = "waiting_session",// Waiting for active session with peer
82
- SENDING = "sending",
79
+ READY = "ready",
83
80
  SENT = "sent",
84
81
  DELIVERED = "delivered",
85
- READ = "read",
86
- FAILED = "failed"
87
- }
88
- export declare enum DiscussionDirection {
89
- INITIATED = "initiated",
90
- RECEIVED = "received"
82
+ READ = "read"
91
83
  }
92
84
  export declare enum MessageType {
93
85
  TEXT = "text",
86
+ ANNOUNCEMENT = "announcement",
94
87
  KEEP_ALIVE = "keep_alive",
95
88
  IMAGE = "image",
96
89
  FILE = "file",
97
90
  AUDIO = "audio",
98
91
  VIDEO = "video"
99
92
  }
93
+ export interface readyAnnouncement {
94
+ announcement_bytes: Uint8Array;
95
+ when_to_send: Date;
96
+ }
97
+ export type SendAnnouncement = null | readyAnnouncement;
100
98
  export interface Discussion {
101
99
  id?: number;
102
100
  ownerUserId: string;
103
101
  contactUserId: string;
102
+ weAccepted: boolean;
103
+ sendAnnouncement: SendAnnouncement;
104
104
  direction: DiscussionDirection;
105
- status: DiscussionStatus;
106
- nextSeeker?: Uint8Array;
107
- initiationAnnouncement?: Uint8Array;
108
- announcementMessage?: string;
109
105
  lastSyncTimestamp?: Date;
110
106
  customName?: string;
107
+ lastAnnouncementMessage?: string;
111
108
  lastMessageId?: number;
112
109
  lastMessageContent?: string;
113
110
  lastMessageTimestamp?: Date;
@@ -147,11 +144,6 @@ export declare class GossipDatabase extends Dexie {
147
144
  getDiscussionsByOwner(ownerUserId: string): Promise<Discussion[]>;
148
145
  getUnreadCountByOwner(ownerUserId: string): Promise<number>;
149
146
  getDiscussionByOwnerAndContact(ownerUserId: string, contactUserId: string): Promise<Discussion | undefined>;
150
- /**
151
- * Get all active discussions with their sync status
152
- * @returns Array of active discussions
153
- */
154
- getActiveDiscussionsByOwner(ownerUserId: string): Promise<Discussion[]>;
155
147
  markMessagesAsRead(ownerUserId: string, contactUserId: string): Promise<void>;
156
148
  getMessagesForContactByOwner(ownerUserId: string, contactUserId: string, limit?: number): Promise<Message[]>;
157
149
  addMessage(message: Omit<Message, 'id'>): Promise<number>;
package/dist/db.js CHANGED
@@ -5,16 +5,11 @@
5
5
  * Provides tables for contacts, messages, discussions, user profiles, and more.
6
6
  */
7
7
  import Dexie from 'dexie';
8
- // Unified discussion interface combining protocol state and UI metadata
9
- export var DiscussionStatus;
10
- (function (DiscussionStatus) {
11
- DiscussionStatus["PENDING"] = "pending";
12
- DiscussionStatus["ACTIVE"] = "active";
13
- DiscussionStatus["CLOSED"] = "closed";
14
- DiscussionStatus["BROKEN"] = "broken";
15
- DiscussionStatus["SEND_FAILED"] = "sendFailed";
16
- DiscussionStatus["RECONNECTING"] = "reconnecting";
17
- })(DiscussionStatus || (DiscussionStatus = {}));
8
+ export var DiscussionDirection;
9
+ (function (DiscussionDirection) {
10
+ DiscussionDirection["RECEIVED"] = "received";
11
+ DiscussionDirection["INITIATED"] = "initiated";
12
+ })(DiscussionDirection || (DiscussionDirection = {}));
18
13
  export var MessageDirection;
19
14
  (function (MessageDirection) {
20
15
  MessageDirection["INCOMING"] = "incoming";
@@ -23,20 +18,15 @@ export var MessageDirection;
23
18
  export var MessageStatus;
24
19
  (function (MessageStatus) {
25
20
  MessageStatus["WAITING_SESSION"] = "waiting_session";
26
- MessageStatus["SENDING"] = "sending";
21
+ MessageStatus["READY"] = "ready";
27
22
  MessageStatus["SENT"] = "sent";
28
23
  MessageStatus["DELIVERED"] = "delivered";
29
24
  MessageStatus["READ"] = "read";
30
- MessageStatus["FAILED"] = "failed";
31
25
  })(MessageStatus || (MessageStatus = {}));
32
- export var DiscussionDirection;
33
- (function (DiscussionDirection) {
34
- DiscussionDirection["INITIATED"] = "initiated";
35
- DiscussionDirection["RECEIVED"] = "received";
36
- })(DiscussionDirection || (DiscussionDirection = {}));
37
26
  export var MessageType;
38
27
  (function (MessageType) {
39
28
  MessageType["TEXT"] = "text";
29
+ MessageType["ANNOUNCEMENT"] = "announcement";
40
30
  MessageType["KEEP_ALIVE"] = "keep_alive";
41
31
  MessageType["IMAGE"] = "image";
42
32
  MessageType["FILE"] = "file";
@@ -165,16 +155,6 @@ export class GossipDatabase extends Dexie {
165
155
  .equals([ownerUserId, contactUserId])
166
156
  .first();
167
157
  }
168
- /**
169
- * Get all active discussions with their sync status
170
- * @returns Array of active discussions
171
- */
172
- async getActiveDiscussionsByOwner(ownerUserId) {
173
- return await this.discussions
174
- .where('[ownerUserId+status]')
175
- .equals([ownerUserId, DiscussionStatus.ACTIVE])
176
- .toArray();
177
- }
178
158
  async markMessagesAsRead(ownerUserId, contactUserId) {
179
159
  await this.messages
180
160
  .where('[ownerUserId+contactUserId+status]')
@@ -216,16 +196,10 @@ export class GossipDatabase extends Dexie {
216
196
  await this.discussions.put({
217
197
  ownerUserId: message.ownerUserId,
218
198
  contactUserId: message.contactUserId,
219
- direction: message.direction === MessageDirection.INCOMING
220
- ? DiscussionDirection.RECEIVED
221
- : DiscussionDirection.INITIATED,
222
- status: DiscussionStatus.PENDING,
223
- nextSeeker: undefined,
224
199
  lastMessageId: messageId,
225
200
  lastMessageContent: message.content,
226
201
  lastMessageTimestamp: message.timestamp,
227
202
  unreadCount: message.direction === MessageDirection.INCOMING ? 1 : 0,
228
- createdAt: new Date(),
229
203
  updatedAt: new Date(),
230
204
  });
231
205
  }
@@ -274,12 +248,15 @@ export class GossipDatabase extends Dexie {
274
248
  // Database instance - initialized lazily or via setDb()
275
249
  let _db = null;
276
250
  let _warnedGlobalDbAccess = false;
251
+ // Store a reference to the Proxy to detect it
252
+ let _proxyDb = null;
277
253
  /**
278
254
  * Get the database instance.
279
255
  * Creates a default instance if none was set via setDb().
280
256
  */
281
257
  export function getDb() {
282
- if (!_db) {
258
+ // Prevent infinite recursion: if _db is the Proxy itself or not a real instance, create a new instance
259
+ if (!_db || _db === _proxyDb || !(_db instanceof GossipDatabase)) {
283
260
  _db = new GossipDatabase();
284
261
  }
285
262
  return _db;
@@ -289,13 +266,24 @@ export function getDb() {
289
266
  * Call this before using any SDK functions if you need a custom db instance.
290
267
  */
291
268
  export function setDb(database) {
292
- _db = database;
269
+ // Prevent setting the Proxy itself to avoid infinite recursion
270
+ // The Proxy is not an instance of GossipDatabase, so we can detect it that way
271
+ if (!(database instanceof GossipDatabase) || database === _proxyDb) {
272
+ // If Proxy is passed, ensure _db exists by calling getDb()
273
+ // This will reuse existing _db if it was already created (e.g., by db.open())
274
+ // or create a new one if needed. This ensures we always use the same instance.
275
+ getDb();
276
+ // Don't overwrite _db - just ensure it exists and is consistent
277
+ }
278
+ else {
279
+ _db = database;
280
+ }
293
281
  }
294
282
  /**
295
283
  * Get the database instance.
296
284
  * Creates a default instance if none was set via setDb().
297
285
  */
298
- export const db = new Proxy({}, {
286
+ export const db = (_proxyDb = new Proxy({}, {
299
287
  get(_target, prop) {
300
288
  if (!_warnedGlobalDbAccess) {
301
289
  _warnedGlobalDbAccess = true;
@@ -308,4 +296,4 @@ export const db = new Proxy({}, {
308
296
  }
309
297
  return value;
310
298
  },
311
- });
299
+ }));