@massalabs/gossip-sdk 0.0.2-dev.20260220142001 → 0.0.2-dev.20260223065033

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 (73) hide show
  1. package/README.md +26 -3
  2. package/dist/core/SdkEventEmitter.d.ts +2 -0
  3. package/dist/core/SdkEventEmitter.js +2 -0
  4. package/dist/{db.d.ts → db/db.d.ts} +14 -3
  5. package/dist/{db.js → db/db.js} +17 -8
  6. package/dist/db/exec-utils.d.ts +19 -0
  7. package/dist/db/exec-utils.js +48 -0
  8. package/dist/db/generated-ddl.d.ts +1 -0
  9. package/dist/db/generated-ddl.js +31 -0
  10. package/dist/db/index.d.ts +3 -0
  11. package/dist/db/index.js +3 -0
  12. package/dist/db/queries/activeSeekers.d.ts +1 -0
  13. package/dist/{queries → db/queries}/activeSeekers.js +0 -5
  14. package/dist/{queries → db/queries}/messages.d.ts +2 -2
  15. package/dist/{queries → db/queries}/messages.js +3 -3
  16. package/dist/{queries → db/queries}/userProfile.d.ts +1 -1
  17. package/dist/{queries → db/queries}/userProfile.js +1 -1
  18. package/dist/db/schema/_helpers.d.ts +26 -0
  19. package/dist/db/schema/_helpers.js +15 -0
  20. package/dist/db/schema/activeSeekers.d.ts +43 -0
  21. package/dist/db/schema/activeSeekers.js +6 -0
  22. package/dist/db/schema/announcementCursors.d.ts +45 -0
  23. package/dist/db/schema/announcementCursors.js +5 -0
  24. package/dist/db/schema/contacts.d.ts +170 -0
  25. package/dist/db/schema/contacts.js +16 -0
  26. package/dist/db/schema/discussions.d.ts +336 -0
  27. package/dist/db/schema/discussions.js +31 -0
  28. package/dist/db/schema/index.d.ts +8 -0
  29. package/dist/db/schema/index.js +8 -0
  30. package/dist/db/schema/messages.d.ts +309 -0
  31. package/dist/db/schema/messages.js +29 -0
  32. package/dist/db/schema/pendingAnnouncements.d.ts +79 -0
  33. package/dist/db/schema/pendingAnnouncements.js +11 -0
  34. package/dist/db/schema/pendingEncryptedMessages.d.ts +79 -0
  35. package/dist/db/schema/pendingEncryptedMessages.js +11 -0
  36. package/dist/db/schema/userProfile.d.ts +208 -0
  37. package/dist/db/schema/userProfile.js +20 -0
  38. package/dist/{sqlite-worker.js → db/sqlite-worker.js} +2 -30
  39. package/dist/db/sqlite.d.ts +77 -0
  40. package/dist/db/sqlite.js +254 -0
  41. package/dist/gossip.d.ts +1 -41
  42. package/dist/gossip.js +8 -10
  43. package/dist/index.d.ts +3 -3
  44. package/dist/index.js +3 -3
  45. package/dist/services/announcement.js +1 -1
  46. package/dist/services/discussion.js +1 -1
  47. package/dist/services/message.d.ts +1 -1
  48. package/dist/services/message.js +2 -3
  49. package/dist/services/refresh.js +1 -1
  50. package/dist/utils/contacts.d.ts +11 -0
  51. package/dist/utils/contacts.js +30 -2
  52. package/dist/utils/discussions.d.ts +1 -1
  53. package/dist/utils/discussions.js +1 -1
  54. package/dist/utils/validation.js +1 -1
  55. package/package.json +2 -1
  56. package/dist/contacts.d.ts +0 -120
  57. package/dist/contacts.js +0 -160
  58. package/dist/queries/activeSeekers.d.ts +0 -2
  59. package/dist/schema.d.ts +0 -1280
  60. package/dist/schema.js +0 -164
  61. package/dist/sqlite.d.ts +0 -79
  62. package/dist/sqlite.js +0 -448
  63. /package/dist/{queries → db/queries}/announcementCursors.d.ts +0 -0
  64. /package/dist/{queries → db/queries}/announcementCursors.js +0 -0
  65. /package/dist/{queries → db/queries}/contacts.d.ts +0 -0
  66. /package/dist/{queries → db/queries}/contacts.js +0 -0
  67. /package/dist/{queries → db/queries}/discussions.d.ts +0 -0
  68. /package/dist/{queries → db/queries}/discussions.js +0 -0
  69. /package/dist/{queries → db/queries}/index.d.ts +0 -0
  70. /package/dist/{queries → db/queries}/index.js +0 -0
  71. /package/dist/{queries → db/queries}/pendingAnnouncements.d.ts +0 -0
  72. /package/dist/{queries → db/queries}/pendingAnnouncements.js +0 -0
  73. /package/dist/{sqlite-worker.d.ts → db/sqlite-worker.d.ts} +0 -0
package/dist/sqlite.js DELETED
@@ -1,448 +0,0 @@
1
- /**
2
- * SQLite initialization module for the Gossip SDK.
3
- *
4
- * Uses wa-sqlite (WASM) with Drizzle ORM's sqlite-proxy driver.
5
- * Two execution paths:
6
- * - Browser (opfsPath set): Web Worker + AccessHandlePoolVFS — OPFS persistence,
7
- * off the main thread. Uses the sync WASM build (wa-sqlite).
8
- * - In-memory (tests): :memory: in-process — no persistence, fast, isolated.
9
- * Uses the sync WASM build with wasmBinary passed directly.
10
- *
11
- * In Phase C the VFS will be swapped for the encrypted PlausibleDeniableVFS.
12
- */
13
- import SQLiteESMFactory from 'wa-sqlite/dist/wa-sqlite.mjs';
14
- import * as SQLite from 'wa-sqlite';
15
- import { drizzle } from 'drizzle-orm/sqlite-proxy';
16
- import * as schema from './schema.js';
17
- function createDefaultState() {
18
- return {
19
- worker: null,
20
- msgId: 0,
21
- pending: new Map(),
22
- lastInsertRowIdCache: 0,
23
- sqlite3: null,
24
- dbHandle: null,
25
- useWorker: false,
26
- drizzleDb: null,
27
- dbLock: Promise.resolve(),
28
- inTransaction: false,
29
- };
30
- }
31
- let db = createDefaultState();
32
- // ---------------------------------------------------------------------------
33
- // Worker communication (browser path)
34
- // ---------------------------------------------------------------------------
35
- function postToWorker(msg
36
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
- ) {
38
- return new Promise((resolve, reject) => {
39
- const id = ++db.msgId;
40
- db.pending.set(id, { resolve, reject });
41
- db.worker.postMessage({ ...msg, id });
42
- });
43
- }
44
- function handleWorkerMessage(e) {
45
- const { id, type, ...rest } = e.data;
46
- const p = db.pending.get(id);
47
- if (!p)
48
- return;
49
- db.pending.delete(id);
50
- if (type === 'error') {
51
- p.reject(new Error(rest.message));
52
- }
53
- else {
54
- p.resolve(rest);
55
- }
56
- }
57
- // ---------------------------------------------------------------------------
58
- // Drizzle instance factory
59
- // ---------------------------------------------------------------------------
60
- function createDrizzleInstance() {
61
- return drizzle(async (sql, params, method) => {
62
- const rows = await execRaw(sql, params);
63
- if (method === 'get') {
64
- return { rows: rows[0] };
65
- }
66
- return { rows };
67
- }, { schema });
68
- }
69
- // ---------------------------------------------------------------------------
70
- // Raw SQL execution
71
- // ---------------------------------------------------------------------------
72
- async function execRaw(sql, params = []) {
73
- // When inside a withTransaction(), the outer lock is already held —
74
- // skip re-acquisition to avoid deadlock.
75
- if (db.inTransaction) {
76
- return execRawDirect(sql, params);
77
- }
78
- const prev = db.dbLock;
79
- let release;
80
- db.dbLock = new Promise(r => (release = r));
81
- await prev;
82
- try {
83
- return await execRawDirect(sql, params);
84
- }
85
- finally {
86
- release();
87
- }
88
- }
89
- async function execRawDirect(sql, params = []) {
90
- if (db.useWorker) {
91
- const result = await postToWorker({ type: 'exec', sql, params });
92
- db.lastInsertRowIdCache = result.lastInsertRowId;
93
- return result.rows;
94
- }
95
- return execRawInProcess(sql, params);
96
- }
97
- // wa-sqlite's column_blob() returns a Uint8Array VIEW into WASM linear memory
98
- // (Module.HEAPU8.subarray). These views become stale after finalize() frees the
99
- // prepared statement, or if WASM memory grows. Copy blob values so callers get
100
- // owned data that survives beyond the current query.
101
- function copyRow(row) {
102
- return row.map(v => (v instanceof Uint8Array ? new Uint8Array(v) : v));
103
- }
104
- async function execRawInProcess(sql, params = []) {
105
- if (!db.sqlite3 || db.dbHandle === null) {
106
- throw new Error('SQLite not initialized');
107
- }
108
- // Simple path for parameterless statements
109
- if (params.length === 0) {
110
- const rows = [];
111
- await db.sqlite3.exec(db.dbHandle, sql, (row) => {
112
- rows.push(copyRow(row));
113
- });
114
- return rows;
115
- }
116
- // Prepared statement path for parameterized queries
117
- const str = db.sqlite3.str_new(db.dbHandle, sql);
118
- try {
119
- const prepared = await db.sqlite3.prepare_v2(db.dbHandle, db.sqlite3.str_value(str));
120
- if (!prepared)
121
- return [];
122
- try {
123
- db.sqlite3.bind_collection(prepared.stmt, params);
124
- const rows = [];
125
- while ((await db.sqlite3.step(prepared.stmt)) === SQLite.SQLITE_ROW) {
126
- rows.push(copyRow(db.sqlite3.row(prepared.stmt)));
127
- }
128
- return rows;
129
- }
130
- finally {
131
- await db.sqlite3.finalize(prepared.stmt);
132
- }
133
- }
134
- finally {
135
- db.sqlite3.str_finish(str);
136
- }
137
- }
138
- // ---------------------------------------------------------------------------
139
- // DDL — CREATE TABLE + indexes
140
- //
141
- // IMPORTANT: This DDL must stay in sync with schema.ts.
142
- // When adding/removing/renaming columns, tables, or indexes, update BOTH files.
143
- // schema.ts is the Drizzle type source; this DDL is the runtime CREATE source.
144
- // ---------------------------------------------------------------------------
145
- const DDL = `
146
- CREATE TABLE IF NOT EXISTS contacts (
147
- id INTEGER PRIMARY KEY AUTOINCREMENT,
148
- ownerUserId TEXT NOT NULL,
149
- userId TEXT NOT NULL,
150
- name TEXT NOT NULL,
151
- avatar TEXT,
152
- publicKeys BLOB NOT NULL,
153
- isOnline INTEGER NOT NULL DEFAULT 0,
154
- lastSeen INTEGER NOT NULL,
155
- createdAt INTEGER NOT NULL
156
- );
157
-
158
- CREATE TABLE IF NOT EXISTS messages (
159
- id INTEGER PRIMARY KEY AUTOINCREMENT,
160
- ownerUserId TEXT NOT NULL,
161
- contactUserId TEXT NOT NULL,
162
- messageId BLOB,
163
- content TEXT NOT NULL,
164
- serializedContent BLOB,
165
- type TEXT NOT NULL,
166
- direction TEXT NOT NULL,
167
- status TEXT NOT NULL,
168
- timestamp INTEGER NOT NULL,
169
- metadata TEXT,
170
- seeker BLOB,
171
- replyTo TEXT,
172
- forwardOf TEXT,
173
- encryptedMessage BLOB,
174
- whenToSend INTEGER
175
- );
176
-
177
- CREATE TABLE IF NOT EXISTS userProfile (
178
- userId TEXT PRIMARY KEY,
179
- username TEXT NOT NULL,
180
- avatar TEXT,
181
- bio TEXT,
182
- status TEXT NOT NULL,
183
- lastSeen INTEGER NOT NULL,
184
- createdAt INTEGER NOT NULL,
185
- updatedAt INTEGER NOT NULL,
186
- lastPublicKeyPush INTEGER,
187
- security TEXT NOT NULL,
188
- session BLOB NOT NULL
189
- );
190
-
191
- CREATE TABLE IF NOT EXISTS discussions (
192
- id INTEGER PRIMARY KEY AUTOINCREMENT,
193
- ownerUserId TEXT NOT NULL,
194
- contactUserId TEXT NOT NULL,
195
- direction TEXT NOT NULL,
196
- status TEXT NOT NULL,
197
- weAccepted INTEGER NOT NULL DEFAULT 0,
198
- sendAnnouncement TEXT,
199
- nextSeeker BLOB,
200
- initiationAnnouncement BLOB,
201
- announcementMessage TEXT,
202
- lastSyncTimestamp INTEGER,
203
- customName TEXT,
204
- lastMessageId INTEGER,
205
- lastMessageContent TEXT,
206
- lastMessageTimestamp INTEGER,
207
- unreadCount INTEGER NOT NULL DEFAULT 0,
208
- createdAt INTEGER NOT NULL,
209
- updatedAt INTEGER NOT NULL
210
- );
211
-
212
- CREATE TABLE IF NOT EXISTS pendingEncryptedMessages (
213
- id INTEGER PRIMARY KEY AUTOINCREMENT,
214
- seeker BLOB NOT NULL,
215
- ciphertext BLOB NOT NULL,
216
- fetchedAt INTEGER NOT NULL
217
- );
218
-
219
- CREATE TABLE IF NOT EXISTS pendingAnnouncements (
220
- id INTEGER PRIMARY KEY AUTOINCREMENT,
221
- announcement BLOB NOT NULL,
222
- fetchedAt INTEGER NOT NULL,
223
- counter TEXT
224
- );
225
-
226
- CREATE TABLE IF NOT EXISTS activeSeekers (
227
- id INTEGER PRIMARY KEY AUTOINCREMENT,
228
- seeker BLOB NOT NULL
229
- );
230
-
231
- -- Indexes: contacts
232
- CREATE INDEX IF NOT EXISTS contacts_owner_user_idx ON contacts(ownerUserId, userId);
233
- CREATE INDEX IF NOT EXISTS contacts_owner_name_idx ON contacts(ownerUserId, name);
234
-
235
- -- Indexes: messages
236
- CREATE INDEX IF NOT EXISTS messages_owner_contact_idx ON messages(ownerUserId, contactUserId);
237
- CREATE INDEX IF NOT EXISTS messages_owner_status_idx ON messages(ownerUserId, status);
238
- CREATE INDEX IF NOT EXISTS messages_owner_contact_status_idx ON messages(ownerUserId, contactUserId, status);
239
- CREATE INDEX IF NOT EXISTS messages_owner_seeker_idx ON messages(ownerUserId, seeker);
240
- CREATE INDEX IF NOT EXISTS messages_owner_contact_dir_idx ON messages(ownerUserId, contactUserId, direction);
241
- CREATE INDEX IF NOT EXISTS messages_owner_dir_status_idx ON messages(ownerUserId, direction, status);
242
- CREATE INDEX IF NOT EXISTS messages_timestamp_idx ON messages(timestamp);
243
-
244
- -- Indexes: userProfile
245
- CREATE INDEX IF NOT EXISTS userProfile_username_idx ON userProfile(username);
246
- CREATE INDEX IF NOT EXISTS userProfile_status_idx ON userProfile(status);
247
-
248
- -- Indexes: discussions
249
- CREATE UNIQUE INDEX IF NOT EXISTS discussions_owner_contact_idx ON discussions(ownerUserId, contactUserId);
250
- CREATE INDEX IF NOT EXISTS discussions_owner_status_idx ON discussions(ownerUserId, status);
251
-
252
- -- Indexes: pendingEncryptedMessages
253
- CREATE INDEX IF NOT EXISTS pending_encrypted_seeker_idx ON pendingEncryptedMessages(seeker);
254
- CREATE INDEX IF NOT EXISTS pending_encrypted_fetchedAt_idx ON pendingEncryptedMessages(fetchedAt);
255
-
256
- -- Indexes: pendingAnnouncements
257
- CREATE UNIQUE INDEX IF NOT EXISTS pending_announcements_announcement_idx ON pendingAnnouncements(announcement);
258
- CREATE INDEX IF NOT EXISTS pending_announcements_fetchedAt_idx ON pendingAnnouncements(fetchedAt);
259
-
260
- -- Indexes: activeSeekers
261
- CREATE INDEX IF NOT EXISTS active_seekers_seeker_idx ON activeSeekers(seeker);
262
-
263
- CREATE TABLE IF NOT EXISTS announcementCursors (
264
- userId TEXT PRIMARY KEY,
265
- counter TEXT NOT NULL
266
- );
267
- `;
268
- // ---------------------------------------------------------------------------
269
- // Migrations — add columns that may be missing from older schema versions.
270
- // ALTER TABLE … ADD COLUMN is idempotent via try/catch (SQLite errors on
271
- // duplicate columns; we simply ignore that error).
272
- // ---------------------------------------------------------------------------
273
- /**
274
- * Run schema migrations that can't be expressed with CREATE TABLE IF NOT EXISTS.
275
- * Each migration is a single ALTER TABLE statement; duplicates are caught and ignored.
276
- */
277
- async function runMigrations(tryExec) {
278
- const alterStatements = [
279
- // v1: add messageId column to messages table
280
- 'ALTER TABLE messages ADD COLUMN messageId BLOB;',
281
- ];
282
- for (const sql of alterStatements) {
283
- await tryExec(sql);
284
- }
285
- }
286
- /** PRAGMAs + DDL combined — sent to Worker during init or run in-process. */
287
- const INIT_SQL = `
288
- PRAGMA journal_mode=MEMORY;
289
- PRAGMA temp_store=MEMORY;
290
- ${DDL}
291
- `;
292
- // ---------------------------------------------------------------------------
293
- // Public API
294
- // ---------------------------------------------------------------------------
295
- /**
296
- * Initialize wa-sqlite and create the Drizzle ORM instance.
297
- * Idempotent — subsequent calls are no-ops.
298
- *
299
- * @param options.opfsPath - Set to persist via OPFS Worker (production).
300
- * Omit for in-memory database (tests).
301
- */
302
- export async function initDb(options = {}) {
303
- if (db.drizzleDb)
304
- return;
305
- if (options.opfsPath) {
306
- // Browser path: spawn Worker with OPFS + AccessHandlePoolVFS.
307
- // The sync WASM build runs in the Worker (no Asyncify needed).
308
- db.worker = new Worker(new URL('./sqlite-worker.ts', import.meta.url), {
309
- type: 'module',
310
- });
311
- db.worker.onmessage = handleWorkerMessage;
312
- db.useWorker = true;
313
- await postToWorker({
314
- type: 'init',
315
- opfsPath: options.opfsPath,
316
- wasmUrl: options.wasmUrl,
317
- initSql: INIT_SQL,
318
- });
319
- // Run migrations via Worker exec (ignore "duplicate column" errors)
320
- await runMigrations(async (sql) => {
321
- try {
322
- await postToWorker({ type: 'exec', sql, params: [] });
323
- }
324
- catch {
325
- // Expected: "duplicate column name: …" when column already exists
326
- }
327
- });
328
- }
329
- else {
330
- // In-memory mode (tests): sync WASM build, in-process, fast, isolated.
331
- const moduleArg = {};
332
- if (options.wasmBinary) {
333
- moduleArg.wasmBinary = options.wasmBinary;
334
- }
335
- const module = await SQLiteESMFactory(moduleArg);
336
- db.sqlite3 = SQLite.Factory(module);
337
- db.dbHandle = await db.sqlite3.open_v2(':memory:');
338
- db.useWorker = false;
339
- await db.sqlite3.exec(db.dbHandle, INIT_SQL);
340
- // Run migrations in-process (ignore "duplicate column" errors)
341
- await runMigrations(async (sql) => {
342
- try {
343
- await db.sqlite3.exec(db.dbHandle, sql);
344
- }
345
- catch {
346
- // Expected: "duplicate column name: …" when column already exists
347
- }
348
- });
349
- }
350
- db.drizzleDb = createDrizzleInstance();
351
- }
352
- /**
353
- * Get the Drizzle ORM database instance.
354
- * Throws if initDb() has not been called.
355
- */
356
- export function getSqliteDb() {
357
- if (!db.drizzleDb) {
358
- throw new Error('SQLite not initialized. Call initDb() first.');
359
- }
360
- return db.drizzleDb;
361
- }
362
- export function isSqliteOpen() {
363
- return db.drizzleDb !== null;
364
- }
365
- /**
366
- * Run a callback inside a SQLite transaction (BEGIN / COMMIT / ROLLBACK).
367
- * All Drizzle operations inside the callback share the same transaction
368
- * and the same dbLock hold, so they cannot interleave with outside queries.
369
- */
370
- export async function withTransaction(fn) {
371
- const prev = db.dbLock;
372
- let release;
373
- db.dbLock = new Promise(r => (release = r));
374
- await prev;
375
- try {
376
- await execRawDirect('BEGIN');
377
- db.inTransaction = true;
378
- try {
379
- const result = await fn();
380
- await execRawDirect('COMMIT');
381
- return result;
382
- }
383
- catch (e) {
384
- await execRawDirect('ROLLBACK');
385
- throw e;
386
- }
387
- finally {
388
- db.inTransaction = false;
389
- }
390
- }
391
- finally {
392
- release();
393
- }
394
- }
395
- export async function clearAllTables() {
396
- const drizzleDb = getSqliteDb();
397
- await drizzleDb.delete(schema.messages);
398
- await drizzleDb.delete(schema.discussions);
399
- await drizzleDb.delete(schema.contacts);
400
- await drizzleDb.delete(schema.userProfile);
401
- await drizzleDb.delete(schema.pendingEncryptedMessages);
402
- await drizzleDb.delete(schema.pendingAnnouncements);
403
- await drizzleDb.delete(schema.activeSeekers);
404
- await drizzleDb.delete(schema.announcementCursors);
405
- }
406
- /**
407
- * Clear only conversation-related tables (contacts, discussions, messages).
408
- * Preserves user profiles and other data.
409
- */
410
- export async function clearConversationTables() {
411
- const drizzleDb = getSqliteDb();
412
- await drizzleDb.delete(schema.messages);
413
- await drizzleDb.delete(schema.discussions);
414
- await drizzleDb.delete(schema.contacts);
415
- }
416
- /**
417
- * Get the last auto-increment row ID inserted via this connection.
418
- * Used after INSERT into tables with INTEGER PRIMARY KEY AUTOINCREMENT.
419
- *
420
- * Browser path: returns the cached value from the last Worker exec response
421
- * (returned atomically with every exec — no race condition).
422
- * Test path: queries directly (same connection, serialized by dbLock).
423
- */
424
- export async function getLastInsertRowId() {
425
- if (db.useWorker) {
426
- return db.lastInsertRowIdCache;
427
- }
428
- const rows = await execRaw('SELECT last_insert_rowid()');
429
- return rows[0][0];
430
- }
431
- /**
432
- * Close the database and release all resources.
433
- * Browser path: sends close to Worker, then terminates it.
434
- * Test path: closes in-process database handle.
435
- * Atomically resets all state by replacing with a fresh default.
436
- */
437
- export async function closeSqlite() {
438
- if (db.useWorker && db.worker) {
439
- await postToWorker({ type: 'close' });
440
- db.worker.terminate();
441
- }
442
- else if (db.dbHandle !== null && db.sqlite3) {
443
- await db.sqlite3.close(db.dbHandle);
444
- }
445
- db = createDefaultState();
446
- }
447
- /** Exported for testing — the raw DDL string used to create tables. */
448
- export const _DDL_FOR_TESTING = DDL;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes