@massalabs/gossip-sdk 0.0.2-dev.20260220143015 → 0.0.2-dev.20260223101931

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 +33 -5
  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 +4 -55
  42. package/dist/gossip.js +35 -51
  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/README.md CHANGED
@@ -33,6 +33,11 @@ await sdk.init();
33
33
  // 2. Open session (login)
34
34
  await sdk.openSession({
35
35
  mnemonic: 'word1 word2 word3 ... word12',
36
+ // Optional: for existing session
37
+ // encryptedSession: savedBlob,
38
+ // encryptionKey, // optional - derived from mnemonic if not provided
39
+ // Optional: for persistence
40
+ // onPersist: async (blob, key) => { await saveToStorage(blob, key); },
36
41
  });
37
42
 
38
43
  // 3. Use the SDK
@@ -308,12 +313,12 @@ await sdk.init({
308
313
 
309
314
  ## Session Persistence
310
315
 
311
- For restoring sessions across app restarts:
316
+ For restoring sessions across app restarts, pass `encryptionKey` (optional — derived from mnemonic if omitted) and `onPersist` when opening the session:
312
317
 
313
318
  ```typescript
314
319
  await sdk.openSession({
315
320
  mnemonic,
316
- encryptionKey,
321
+ encryptionKey, // optional
317
322
  onPersist: async (blob, key) => {
318
323
  await storage.save({ session: blob });
319
324
  },
@@ -345,6 +350,26 @@ import type {
345
350
  import { SessionStatus, SdkEventType } from '@massalabs/gossip-sdk';
346
351
  ```
347
352
 
353
+ ## Database
354
+
355
+ SQLite via [wa-sqlite](https://github.com/nicolo-ribaudo/wa-sqlite) (WASM) with [Drizzle ORM](https://orm.drizzle.team). Data is persisted to IndexedDB using `IDBBatchAtomicVFS`.
356
+
357
+ ### Schema
358
+
359
+ Schema is defined in `src/db/schema/` with one file per table. Drizzle-kit generates SQL migrations from the schema.
360
+
361
+ ### Migrations
362
+
363
+ Migrations live in `drizzle/` and are applied automatically on `initDb()` via Drizzle's built-in migrator.
364
+
365
+ When you change the schema, regenerate migrations:
366
+
367
+ ```bash
368
+ npm run db:generate
369
+ ```
370
+
371
+ This runs `drizzle-kit generate`, which diffs the schema against existing migrations and outputs a new `.sql` file in `drizzle/`. Commit the generated migration alongside your schema change.
372
+
348
373
  ## Testing
349
374
 
350
375
  ```bash
@@ -358,10 +383,14 @@ Tests use wa-sqlite with in-memory databases for fast, isolated execution.
358
383
 
359
384
  ```
360
385
  gossip-sdk/
386
+ ├── drizzle/ # Generated SQL migrations
361
387
  ├── src/
362
388
  │ ├── gossipSdk.ts # SDK class & factory
363
- │ ├── db.ts # Database (SQLite) implementation
364
- │ ├── contacts.ts # Contact operations
389
+ │ ├── db/
390
+ ├── index.ts # Barrel export (all DB access goes through here)
391
+ │ │ ├── schema/ # Drizzle table definitions (one file per table)
392
+ │ │ ├── queries/ # Query functions
393
+ │ │ └── sqlite.ts # SQLite init, migration, connection
365
394
  │ ├── api/
366
395
  │ │ └── messageProtocol/ # REST protocol implementation
367
396
  │ ├── config/
@@ -376,7 +405,6 @@ gossip-sdk/
376
405
  │ │ ├── discussion.ts # Discussion service
377
406
  │ │ ├── announcement.ts # Announcement service
378
407
  │ │ └── refresh.ts # Session refresh service
379
- │ ├── types/ # Type definitions
380
408
  │ ├── utils/ # Utility modules
381
409
  │ └── wasm/ # WASM module wrappers
382
410
  └── test/ # Test files
@@ -13,6 +13,7 @@ export declare enum SdkEventType {
13
13
  SESSION_CREATED = "sessionCreated",
14
14
  SESSION_RENEWED = "sessionRenewed",
15
15
  SESSION_ACCEPTED = "sessionAccepted",
16
+ SEEKERS_UPDATED = "seekersUpdated",
16
17
  ERROR = "error"
17
18
  }
18
19
  export interface SdkEventHandlers {
@@ -24,6 +25,7 @@ export interface SdkEventHandlers {
24
25
  [SdkEventType.SESSION_CREATED]: (discussion: Discussion) => void;
25
26
  [SdkEventType.SESSION_RENEWED]: (discussion: Discussion) => void;
26
27
  [SdkEventType.SESSION_ACCEPTED]: (contactUserId: string) => void;
28
+ [SdkEventType.SEEKERS_UPDATED]: (seekers: Uint8Array[]) => void;
27
29
  [SdkEventType.ERROR]: (error: Error, context: string) => void;
28
30
  }
29
31
  export declare class SdkEventEmitter {
@@ -16,6 +16,7 @@ export var SdkEventType;
16
16
  SdkEventType["SESSION_CREATED"] = "sessionCreated";
17
17
  SdkEventType["SESSION_RENEWED"] = "sessionRenewed";
18
18
  SdkEventType["SESSION_ACCEPTED"] = "sessionAccepted";
19
+ SdkEventType["SEEKERS_UPDATED"] = "seekersUpdated";
19
20
  SdkEventType["ERROR"] = "error";
20
21
  })(SdkEventType || (SdkEventType = {}));
21
22
  // ─────────────────────────────────────────────────────────────────────────────
@@ -36,6 +37,7 @@ export class SdkEventEmitter {
36
37
  [SdkEventType.SESSION_CREATED]: new Set(),
37
38
  [SdkEventType.SESSION_RENEWED]: new Set(),
38
39
  [SdkEventType.SESSION_ACCEPTED]: new Set(),
40
+ [SdkEventType.SEEKERS_UPDATED]: new Set(),
39
41
  [SdkEventType.ERROR]: new Set(),
40
42
  }
41
43
  });
@@ -106,11 +106,21 @@ export interface ReadyAnnouncement {
106
106
  export type SendAnnouncement = null | ReadyAnnouncement;
107
107
  /** Serialize a SendAnnouncement to a JSON string for SQLite text column */
108
108
  export declare function serializeSendAnnouncement(announcement: ReadyAnnouncement): string;
109
- /** Deserialize a SendAnnouncement JSON string from SQLite back to an object */
109
+ /** Deserialize a SendAnnouncement JSON string from SQLite back to an object.
110
+ * Throws a descriptive error if the stored JSON is malformed. */
110
111
  export declare function deserializeSendAnnouncement(json: string): ReadyAnnouncement;
111
- /** Convert a raw SQLite discussion row to a Discussion object.
112
+ /**
113
+ * Shape required by rowToDiscussion — matches Drizzle's inferred DiscussionRow
114
+ * without importing from schema (avoids circular dependency).
115
+ */
116
+ interface DiscussionRowLike {
117
+ sendAnnouncement: string | null;
118
+ announcementMessage: string | null;
119
+ [key: string]: unknown;
120
+ }
121
+ /** Convert a Drizzle discussion row to a domain Discussion object.
112
122
  * Deserializes sendAnnouncement from JSON text to SendAnnouncement. */
113
- export declare function rowToDiscussion(row: Record<string, unknown>): Discussion;
123
+ export declare function rowToDiscussion(row: DiscussionRowLike): Discussion;
114
124
  export interface Discussion {
115
125
  id?: number;
116
126
  ownerUserId: string;
@@ -148,3 +158,4 @@ export interface ActiveSeeker {
148
158
  id?: number;
149
159
  seeker: Uint8Array;
150
160
  }
161
+ export {};
@@ -53,20 +53,29 @@ export function serializeSendAnnouncement(announcement) {
53
53
  when_to_send: announcement.when_to_send.toISOString(),
54
54
  });
55
55
  }
56
- /** Deserialize a SendAnnouncement JSON string from SQLite back to an object */
56
+ /** Deserialize a SendAnnouncement JSON string from SQLite back to an object.
57
+ * Throws a descriptive error if the stored JSON is malformed. */
57
58
  export function deserializeSendAnnouncement(json) {
58
- const parsed = JSON.parse(json);
59
- return {
60
- announcement_bytes: new Uint8Array(parsed.announcement_bytes),
61
- when_to_send: new Date(parsed.when_to_send),
62
- };
59
+ try {
60
+ const parsed = JSON.parse(json);
61
+ if (!parsed || !parsed.announcement_bytes || !parsed.when_to_send) {
62
+ throw new Error('missing required fields');
63
+ }
64
+ return {
65
+ announcement_bytes: new Uint8Array(parsed.announcement_bytes),
66
+ when_to_send: new Date(parsed.when_to_send),
67
+ };
68
+ }
69
+ catch (e) {
70
+ throw new Error(`Failed to deserialize SendAnnouncement: ${e instanceof Error ? e.message : e}`);
71
+ }
63
72
  }
64
- /** Convert a raw SQLite discussion row to a Discussion object.
73
+ /** Convert a Drizzle discussion row to a domain Discussion object.
65
74
  * Deserializes sendAnnouncement from JSON text to SendAnnouncement. */
66
75
  export function rowToDiscussion(row) {
67
76
  return {
68
77
  ...row,
69
- sendAnnouncement: typeof row.sendAnnouncement === 'string'
78
+ sendAnnouncement: row.sendAnnouncement !== null
70
79
  ? deserializeSendAnnouncement(row.sendAnnouncement)
71
80
  : null,
72
81
  lastAnnouncementMessage: row.announcementMessage ?? undefined,
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared SQLite execution utilities.
3
+ *
4
+ * Used by both sqlite.ts (in-process path) and sqlite-worker.ts (Worker path)
5
+ * to avoid duplicating the wa-sqlite prepare/bind/step/finalize logic.
6
+ */
7
+ import * as SQLite from 'wa-sqlite';
8
+ export type SqliteAPI = ReturnType<typeof SQLite.Factory>;
9
+ /**
10
+ * Copy blob values out of WASM linear memory.
11
+ * wa-sqlite's column_blob() returns a Uint8Array VIEW into Module.HEAPU8.
12
+ * These views become stale after finalize() or memory growth.
13
+ */
14
+ export declare function copyRow(row: unknown[]): unknown[];
15
+ /**
16
+ * Execute a SQL statement against a wa-sqlite database handle.
17
+ * Handles both parameterless (exec) and parameterized (prepare_v2) paths.
18
+ */
19
+ export declare function execStatements(sqlite3: SqliteAPI, dbHandle: number, sql: string, params?: unknown[]): Promise<unknown[][]>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shared SQLite execution utilities.
3
+ *
4
+ * Used by both sqlite.ts (in-process path) and sqlite-worker.ts (Worker path)
5
+ * to avoid duplicating the wa-sqlite prepare/bind/step/finalize logic.
6
+ */
7
+ import * as SQLite from 'wa-sqlite';
8
+ /**
9
+ * Copy blob values out of WASM linear memory.
10
+ * wa-sqlite's column_blob() returns a Uint8Array VIEW into Module.HEAPU8.
11
+ * These views become stale after finalize() or memory growth.
12
+ */
13
+ export function copyRow(row) {
14
+ return row.map(v => (v instanceof Uint8Array ? new Uint8Array(v) : v));
15
+ }
16
+ /**
17
+ * Execute a SQL statement against a wa-sqlite database handle.
18
+ * Handles both parameterless (exec) and parameterized (prepare_v2) paths.
19
+ */
20
+ export async function execStatements(sqlite3, dbHandle, sql, params = []) {
21
+ if (params.length === 0) {
22
+ const rows = [];
23
+ await sqlite3.exec(dbHandle, sql, (row) => {
24
+ rows.push(copyRow(row));
25
+ });
26
+ return rows;
27
+ }
28
+ const str = sqlite3.str_new(dbHandle, sql);
29
+ try {
30
+ const prepared = await sqlite3.prepare_v2(dbHandle, sqlite3.str_value(str));
31
+ if (!prepared)
32
+ return [];
33
+ try {
34
+ sqlite3.bind_collection(prepared.stmt, params);
35
+ const rows = [];
36
+ while ((await sqlite3.step(prepared.stmt)) === SQLite.SQLITE_ROW) {
37
+ rows.push(copyRow(sqlite3.row(prepared.stmt)));
38
+ }
39
+ return rows;
40
+ }
41
+ finally {
42
+ await sqlite3.finalize(prepared.stmt);
43
+ }
44
+ }
45
+ finally {
46
+ sqlite3.str_finish(str);
47
+ }
48
+ }
@@ -0,0 +1 @@
1
+ export declare const DDL: string[];
@@ -0,0 +1,31 @@
1
+ // Auto-generated from drizzle migrations — do not edit manually.
2
+ // Regenerate with: npm run db:generate
3
+ export const DDL = [
4
+ 'CREATE TABLE IF NOT EXISTS `activeSeekers` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`seeker` blob NOT NULL\n);',
5
+ 'CREATE INDEX IF NOT EXISTS `active_seekers_seeker_idx` ON `activeSeekers` (`seeker`);',
6
+ 'CREATE TABLE IF NOT EXISTS `announcementCursors` (\n\t`userId` text PRIMARY KEY NOT NULL,\n\t`counter` text NOT NULL\n);',
7
+ 'CREATE TABLE IF NOT EXISTS `contacts` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`ownerUserId` text NOT NULL,\n\t`userId` text NOT NULL,\n\t`name` text NOT NULL,\n\t`avatar` text,\n\t`publicKeys` blob NOT NULL,\n\t`isOnline` integer NOT NULL,\n\t`lastSeen` integer NOT NULL,\n\t`createdAt` integer NOT NULL\n);',
8
+ 'CREATE INDEX IF NOT EXISTS `contacts_owner_user_idx` ON `contacts` (`ownerUserId`,`userId`);',
9
+ 'CREATE INDEX IF NOT EXISTS `contacts_owner_name_idx` ON `contacts` (`ownerUserId`,`name`);',
10
+ 'CREATE TABLE IF NOT EXISTS `discussions` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`ownerUserId` text NOT NULL,\n\t`contactUserId` text NOT NULL,\n\t`weAccepted` integer DEFAULT false NOT NULL,\n\t`sendAnnouncement` text,\n\t`direction` text NOT NULL,\n\t`status` text NOT NULL,\n\t`nextSeeker` blob,\n\t`initiationAnnouncement` blob,\n\t`announcementMessage` text,\n\t`lastSyncTimestamp` integer,\n\t`customName` text,\n\t`lastMessageId` integer,\n\t`lastMessageContent` text,\n\t`lastMessageTimestamp` integer,\n\t`unreadCount` integer DEFAULT 0 NOT NULL,\n\t`createdAt` integer NOT NULL,\n\t`updatedAt` integer NOT NULL\n);',
11
+ 'CREATE UNIQUE INDEX IF NOT EXISTS `discussions_owner_contact_idx` ON `discussions` (`ownerUserId`,`contactUserId`);',
12
+ 'CREATE INDEX IF NOT EXISTS `discussions_owner_status_idx` ON `discussions` (`ownerUserId`,`status`);',
13
+ 'CREATE TABLE IF NOT EXISTS `messages` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`ownerUserId` text NOT NULL,\n\t`contactUserId` text NOT NULL,\n\t`messageId` blob,\n\t`content` text NOT NULL,\n\t`serializedContent` blob,\n\t`type` text NOT NULL,\n\t`direction` text NOT NULL,\n\t`status` text NOT NULL,\n\t`timestamp` integer NOT NULL,\n\t`metadata` text,\n\t`seeker` blob,\n\t`replyTo` text,\n\t`forwardOf` text,\n\t`encryptedMessage` blob,\n\t`whenToSend` integer\n);',
14
+ 'CREATE INDEX IF NOT EXISTS `messages_owner_contact_idx` ON `messages` (`ownerUserId`,`contactUserId`);',
15
+ 'CREATE INDEX IF NOT EXISTS `messages_owner_status_idx` ON `messages` (`ownerUserId`,`status`);',
16
+ 'CREATE INDEX IF NOT EXISTS `messages_owner_contact_status_idx` ON `messages` (`ownerUserId`,`contactUserId`,`status`);',
17
+ 'CREATE INDEX IF NOT EXISTS `messages_owner_seeker_idx` ON `messages` (`ownerUserId`,`seeker`);',
18
+ 'CREATE INDEX IF NOT EXISTS `messages_owner_contact_dir_idx` ON `messages` (`ownerUserId`,`contactUserId`,`direction`);',
19
+ 'CREATE INDEX IF NOT EXISTS `messages_owner_dir_status_idx` ON `messages` (`ownerUserId`,`direction`,`status`);',
20
+ 'CREATE INDEX IF NOT EXISTS `messages_timestamp_idx` ON `messages` (`timestamp`);',
21
+ 'CREATE TABLE IF NOT EXISTS `pendingAnnouncements` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`announcement` blob NOT NULL,\n\t`fetchedAt` integer NOT NULL,\n\t`counter` text\n);',
22
+ 'CREATE UNIQUE INDEX IF NOT EXISTS `pending_announcements_announcement_idx` ON `pendingAnnouncements` (`announcement`);',
23
+ 'CREATE INDEX IF NOT EXISTS `pending_announcements_fetchedAt_idx` ON `pendingAnnouncements` (`fetchedAt`);',
24
+ 'CREATE TABLE IF NOT EXISTS `pendingEncryptedMessages` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`seeker` blob NOT NULL,\n\t`ciphertext` blob NOT NULL,\n\t`fetchedAt` integer NOT NULL\n);',
25
+ 'CREATE INDEX IF NOT EXISTS `pending_encrypted_seeker_idx` ON `pendingEncryptedMessages` (`seeker`);',
26
+ 'CREATE INDEX IF NOT EXISTS `pending_encrypted_fetchedAt_idx` ON `pendingEncryptedMessages` (`fetchedAt`);',
27
+ 'CREATE TABLE IF NOT EXISTS `userProfile` (\n\t`userId` text PRIMARY KEY NOT NULL,\n\t`username` text NOT NULL,\n\t`avatar` text,\n\t`bio` text,\n\t`status` text NOT NULL,\n\t`lastSeen` integer NOT NULL,\n\t`createdAt` integer NOT NULL,\n\t`updatedAt` integer NOT NULL,\n\t`lastPublicKeyPush` integer,\n\t`security` text NOT NULL,\n\t`session` blob NOT NULL\n);',
28
+ 'CREATE INDEX IF NOT EXISTS `userProfile_username_idx` ON `userProfile` (`username`);',
29
+ 'CREATE INDEX IF NOT EXISTS `userProfile_status_idx` ON `userProfile` (`status`);',
30
+ 'CREATE INDEX IF NOT EXISTS `messages_owner_contact_msgid_idx` ON `messages` (`ownerUserId`,`contactUserId`,`messageId`);',
31
+ ];
@@ -0,0 +1,3 @@
1
+ export * from './db';
2
+ export * from './queries';
3
+ export * from './sqlite';
@@ -0,0 +1,3 @@
1
+ export * from './db';
2
+ export * from './queries';
3
+ export * from './sqlite';
@@ -0,0 +1 @@
1
+ export declare function replaceActiveSeekers(seekers: Uint8Array[]): Promise<void>;
@@ -1,9 +1,5 @@
1
1
  import * as schema from '../schema';
2
2
  import { getSqliteDb, withTransaction } from '../sqlite';
3
- let onSeekersUpdated = null;
4
- export function setOnSeekersUpdated(cb) {
5
- onSeekersUpdated = cb;
6
- }
7
3
  export async function replaceActiveSeekers(seekers) {
8
4
  await withTransaction(async () => {
9
5
  const db = getSqliteDb();
@@ -14,5 +10,4 @@ export async function replaceActiveSeekers(seekers) {
14
10
  .values(seekers.map(seeker => ({ seeker })));
15
11
  }
16
12
  });
17
- onSeekersUpdated?.(seekers);
18
13
  }
@@ -1,5 +1,5 @@
1
1
  import * as schema from '../schema';
2
- import { MessageStatus } from '../db';
2
+ import { MessageStatus } from '../../db/db';
3
3
  export type MessageRow = typeof schema.messages.$inferSelect;
4
4
  export type MessageInsert = typeof schema.messages.$inferInsert;
5
5
  export declare function getMessageById(id: number): Promise<MessageRow | undefined>;
@@ -14,7 +14,7 @@ export declare function deleteDeliveredKeepAliveMessages(ownerUserId: string): P
14
14
  export declare function getOutgoingSentMessagesByOwner(ownerUserId: string): Promise<MessageRow[]>;
15
15
  export declare function getWaitingMessageCount(ownerUserId: string, contactUserId: string): Promise<number>;
16
16
  export declare function getSendQueueMessages(ownerUserId: string, contactUserId: string): Promise<MessageRow[]>;
17
- export declare function getMessagesByStatus(status: MessageStatus): Promise<MessageRow[]>;
17
+ export declare function getMessagesByStatus(ownerUserId: string, status: MessageStatus): Promise<MessageRow[]>;
18
18
  /**
19
19
  * Reset outgoing messages to WAITING_SESSION, clearing encryption data.
20
20
  * @param statuses - If provided, only reset messages with these statuses.
@@ -1,7 +1,7 @@
1
1
  import { eq, and, sql, inArray, asc } from 'drizzle-orm';
2
2
  import * as schema from '../schema';
3
3
  import { getSqliteDb, getLastInsertRowId } from '../sqlite';
4
- import { MessageDirection, MessageStatus, MessageType } from '../db';
4
+ import { MessageDirection, MessageStatus, MessageType } from '../../db/db';
5
5
  export async function getMessageById(id) {
6
6
  return getSqliteDb()
7
7
  .select()
@@ -82,11 +82,11 @@ export async function getSendQueueMessages(ownerUserId, contactUserId) {
82
82
  .orderBy(asc(schema.messages.timestamp), asc(schema.messages.id))
83
83
  .all();
84
84
  }
85
- export async function getMessagesByStatus(status) {
85
+ export async function getMessagesByStatus(ownerUserId, status) {
86
86
  return getSqliteDb()
87
87
  .select()
88
88
  .from(schema.messages)
89
- .where(eq(schema.messages.status, status))
89
+ .where(and(eq(schema.messages.ownerUserId, ownerUserId), eq(schema.messages.status, status)))
90
90
  .all();
91
91
  }
92
92
  /**
@@ -6,7 +6,7 @@ export type UserProfileInsert = typeof schema.userProfile.$inferInsert;
6
6
  export declare function rowToUserProfile(row: UserProfileRow): UserProfile;
7
7
  /** Convert a domain UserProfile to a DB-ready insert row (security as JSON string). */
8
8
  export declare function userProfileToRow(profile: UserProfile): UserProfileInsert;
9
- export declare function getUserProfileField(userId: string): Promise<UserProfileRow | undefined>;
9
+ export declare function getUserProfileById(userId: string): Promise<UserProfileRow | undefined>;
10
10
  export declare function updateUserProfileById(userId: string, data: Partial<UserProfileInsert>): Promise<void>;
11
11
  export declare function getUserProfileByUsernameLower(username: string): Promise<{
12
12
  userId: string;
@@ -51,7 +51,7 @@ export function userProfileToRow(profile) {
51
51
  lastPublicKeyPush: profile.lastPublicKeyPush ?? null,
52
52
  };
53
53
  }
54
- export async function getUserProfileField(userId) {
54
+ export async function getUserProfileById(userId) {
55
55
  return getSqliteDb()
56
56
  .select()
57
57
  .from(schema.userProfile)
@@ -0,0 +1,26 @@
1
+ export declare const bytes: {
2
+ (): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
3
+ name: "";
4
+ dataType: "custom";
5
+ columnType: "SQLiteCustomColumn";
6
+ data: Uint8Array<ArrayBufferLike>;
7
+ driverParam: Uint8Array<ArrayBufferLike>;
8
+ enumValues: undefined;
9
+ }>;
10
+ <TConfig extends Record<string, any>>(fieldConfig?: TConfig | undefined): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
11
+ name: "";
12
+ dataType: "custom";
13
+ columnType: "SQLiteCustomColumn";
14
+ data: Uint8Array<ArrayBufferLike>;
15
+ driverParam: Uint8Array<ArrayBufferLike>;
16
+ enumValues: undefined;
17
+ }>;
18
+ <TName extends string>(dbName: TName, fieldConfig?: unknown): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
19
+ name: TName;
20
+ dataType: "custom";
21
+ columnType: "SQLiteCustomColumn";
22
+ data: Uint8Array<ArrayBufferLike>;
23
+ driverParam: Uint8Array<ArrayBufferLike>;
24
+ enumValues: undefined;
25
+ }>;
26
+ };
@@ -0,0 +1,15 @@
1
+ import { customType } from 'drizzle-orm/sqlite-core';
2
+ // Custom blob type — wa-sqlite returns Uint8Array natively for BLOB columns.
3
+ // Drizzle's built-in blob mode converts to/from hex strings, which breaks
4
+ // with wa-sqlite's direct Uint8Array handling. This custom type passes through.
5
+ export const bytes = customType({
6
+ dataType() {
7
+ return 'blob';
8
+ },
9
+ fromDriver(value) {
10
+ return value;
11
+ },
12
+ toDriver(value) {
13
+ return value;
14
+ },
15
+ });
@@ -0,0 +1,43 @@
1
+ export declare const activeSeekers: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
2
+ name: "activeSeekers";
3
+ schema: undefined;
4
+ columns: {
5
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
6
+ name: "id";
7
+ tableName: "activeSeekers";
8
+ dataType: "number";
9
+ columnType: "SQLiteInteger";
10
+ data: number;
11
+ driverParam: number;
12
+ notNull: true;
13
+ hasDefault: true;
14
+ isPrimaryKey: true;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: false;
17
+ enumValues: undefined;
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {}>;
22
+ seeker: import("drizzle-orm/sqlite-core").SQLiteColumn<{
23
+ name: "seeker";
24
+ tableName: "activeSeekers";
25
+ dataType: "custom";
26
+ columnType: "SQLiteCustomColumn";
27
+ data: Uint8Array<ArrayBufferLike>;
28
+ driverParam: Uint8Array<ArrayBufferLike>;
29
+ notNull: true;
30
+ hasDefault: false;
31
+ isPrimaryKey: false;
32
+ isAutoincrement: false;
33
+ hasRuntimeDefault: false;
34
+ enumValues: undefined;
35
+ baseColumn: never;
36
+ identity: undefined;
37
+ generated: undefined;
38
+ }, {}, {
39
+ sqliteColumnBuilderBrand: "SQLiteCustomColumnBuilderBrand";
40
+ }>;
41
+ };
42
+ dialect: "sqlite";
43
+ }>;
@@ -0,0 +1,6 @@
1
+ import { sqliteTable, integer, index } from 'drizzle-orm/sqlite-core';
2
+ import { bytes } from './_helpers';
3
+ export const activeSeekers = sqliteTable('activeSeekers', {
4
+ id: integer('id').primaryKey({ autoIncrement: true }),
5
+ seeker: bytes('seeker').notNull(),
6
+ }, table => [index('active_seekers_seeker_idx').on(table.seeker)]);
@@ -0,0 +1,45 @@
1
+ export declare const announcementCursors: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
2
+ name: "announcementCursors";
3
+ schema: undefined;
4
+ columns: {
5
+ userId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
6
+ name: "userId";
7
+ tableName: "announcementCursors";
8
+ dataType: "string";
9
+ columnType: "SQLiteText";
10
+ data: string;
11
+ driverParam: string;
12
+ notNull: true;
13
+ hasDefault: false;
14
+ isPrimaryKey: true;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: false;
17
+ enumValues: [string, ...string[]];
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {
22
+ length: number | undefined;
23
+ }>;
24
+ counter: import("drizzle-orm/sqlite-core").SQLiteColumn<{
25
+ name: "counter";
26
+ tableName: "announcementCursors";
27
+ dataType: "string";
28
+ columnType: "SQLiteText";
29
+ data: string;
30
+ driverParam: string;
31
+ notNull: true;
32
+ hasDefault: false;
33
+ isPrimaryKey: false;
34
+ isAutoincrement: false;
35
+ hasRuntimeDefault: false;
36
+ enumValues: [string, ...string[]];
37
+ baseColumn: never;
38
+ identity: undefined;
39
+ generated: undefined;
40
+ }, {}, {
41
+ length: number | undefined;
42
+ }>;
43
+ };
44
+ dialect: "sqlite";
45
+ }>;
@@ -0,0 +1,5 @@
1
+ import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
2
+ export const announcementCursors = sqliteTable('announcementCursors', {
3
+ userId: text('userId').primaryKey(),
4
+ counter: text('counter').notNull(),
5
+ });